<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Comepaolo</title>
    <description>Code, thoughts, and tales from Jesi e dintorni.</description>
    <link>https://riccardomaldini.it/</link>
    <atom:link href="https://riccardomaldini.it/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Wed, 02 Sep 2026 15:11:45 +0000</pubDate>
    <lastBuildDate>Wed, 02 Sep 2026 15:11:45 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>From CQRS to Event-Driven Architecture: an architectural pattern walkthrough</title>
        <description>&lt;p&gt;It all started with the same suggestion arriving from two different directions.&lt;/p&gt;

&lt;p&gt;Summer 2024, the first one; I was working with my previous company on a platform for portfolio monitoring. The Spring Boot backend exposed multiple endpoints queried by the frontend, with a REST API in place for that. Some of these API calls became quite complex, for different reasons. Behind one of these single HTTP connections the server validated, wrote to a database, called two external services, and only then answered. When one of those services had a bad afternoon, the user got a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;500&lt;/code&gt;. Not because their request was wrong, but because something three hops away was slow.&lt;/p&gt;

&lt;p&gt;A colleague I trust gave me advice on that, on more than one occasion: &lt;strong&gt;separate the request from its execution, let events carry the work across, and go read about CQRS&lt;/strong&gt;. I tried to explore that and other patterns here and there, but I never put proper time into it.&lt;/p&gt;

&lt;p&gt;Spring 2026, the second one; in a brainstorming session, my manager suggested CQRS and Event Sourcing for an application we were designing, this time for a completely different reason: &lt;strong&gt;audit logs out of the box, no separate history table to maintain&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The same words, for two problems that had nothing to do with each other; that was enough to send me reading properly. So take my hand, and let’s walk through that journey together.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-08-21-from-cqrs-to-event-driven-architecture/get-deep.jpg&quot; alt=&quot;Let&apos;s get deep on this together :)&quot; /&gt;
  &lt;figcaption&gt;Let&apos;s get deep on this together :)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;What I want to do here is connect a few tiles that were floating around separately in my head, and possibly in yours too: five patterns that keep coming up in the same conversations, what each one is really for, what it costs, and how they relate to one another. We start from a concrete need, that slow endpoint mentioned at first, and we end up somewhere far bigger than it.&lt;/p&gt;

&lt;p&gt;These are the five ideas we’ll go through:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;CQRS.&lt;/strong&gt; Split reads and writes into separate code paths.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Event Sourcing.&lt;/strong&gt; Store facts instead of state.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Asynchronous Request-Reply.&lt;/strong&gt; Accept the request, execute later.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Transactional Outbox (+ Listen To Yourself).&lt;/strong&gt; One transaction for your state and your message.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Event-Driven Architecture.&lt;/strong&gt; Publish the fact, let the rest of the company consume it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To compare them fairly, every step gets pointed at the same example: an &lt;strong&gt;order management system&lt;/strong&gt;, the back end of any online shop. A customer places an order, changes the delivery address ten minutes later, pays, and eventually the thing ships. A small domain, familiar to everybody, and with one property that becomes important later.&lt;/p&gt;

&lt;p&gt;For every step I’ll ask the same two things: what it fixes on that example, and what it charges in exchange. Plus a third one that mattered more to me at the time, which is whether it gets us any closer to fixing that endpoint.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-zero-the-baseline-architecture-we-all-know&quot;&gt;Step zero: The baseline architecture we all know&lt;/h2&gt;

&lt;p&gt;It’s worth being precise about what we’re comparing against, because it’s tempting to turn it into an easy target, and it doesn’t deserve that. The classic MVC layered application has four properties, and they travel together:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Reads and writes go through the &lt;strong&gt;same stack&lt;/strong&gt;: same endpoint, same service, same DAO.&lt;/li&gt;
  &lt;li&gt;They use the &lt;strong&gt;same model&lt;/strong&gt;. One &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Order&lt;/code&gt; class that is the business model, the persistence model, and (with a DTO or two of polish) the thing you send over the wire.&lt;/li&gt;
  &lt;li&gt;It’s &lt;strong&gt;one deployment unit&lt;/strong&gt;, usually against &lt;strong&gt;one data store&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Writes &lt;strong&gt;mutate state in place&lt;/strong&gt;. You update a row, the old value is gone.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-08-21-from-cqrs-to-event-driven-architecture/01-layered.png&quot; alt=&quot;The classic layered stack: client, endpoint, business service, DAO and database, each layer holding its own shape of the same Order&quot; /&gt;
  &lt;figcaption&gt;One path, and the same Order re-shaped four times along it&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This usually works. I want to say it clearly, because everything after this section is about alternatives and it would be easy to read the whole thing as a condemnation. Plenty of applications run on exactly this for years, and the people maintaining them sleep fine.&lt;/p&gt;

&lt;p&gt;But there are cracks, and each one is the reason for one of the five steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;The data model is a compromise.&lt;/strong&gt; One schema serves both writing &lt;em&gt;and&lt;/em&gt; reading. The form of that schema is usually write-optimized, meaning that we often end up using some tricks to obtain read-optimized views on top of it (ex., using database views). We hide a conflict of interest.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;You can’t scale reads and writes independently.&lt;/strong&gt; If your traffic is 90% reads, read replicas solve that today, cheaply, with no architectural change whatsoever, giving you &lt;em&gt;more of the same shape&lt;/em&gt;. But if the shape itself is the problem, more copies of it don’t help, and you have to rewrite the whole stack.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;There’s no history.&lt;/strong&gt; No snapshot, no replay, no rollback, no audit trail. Not because it’s impossible, but because &lt;em&gt;you have to write that code yourself&lt;/em&gt;, as a feature.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;It pulls toward a monolith.&lt;/strong&gt; Nothing forbids splitting it, but the pull of “one model, one store, one deploy” is real.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, what should we do when these cracks start to look more like reefs?&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-one-cqrs-and-why-it-is-much-smaller-than-you-think&quot;&gt;Step one: CQRS, and why it is much smaller than you think&lt;/h2&gt;

&lt;p&gt;Let’s start with the definition. CQRS is an architectural pattern, and stands for Command Query Responsibility Segregation. &lt;strong&gt;Commands&lt;/strong&gt; are write operations, &lt;strong&gt;queries&lt;/strong&gt; are read operations, and the claim is simply that the two don’t have to share a model. The idea behind the pattern is simple: one code path handles writes, shaped around the business rules it has to enforce. Another handles reads, shaped around the questions the UI actually asks. They can share a database. They just stop sharing a &lt;em&gt;model&lt;/em&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-08-21-from-cqrs-to-event-driven-architecture/02-cqrs.png&quot; alt=&quot;Plain CQRS: an order command service with a rich write model and an order query service with a flat read model, both against the same database&quot; /&gt;
  &lt;figcaption&gt;The split is in the code, not in the infrastructure&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This alone addresses the data modelling compromise crack mentioned before. Your write model can be a rich domain object with all its rules, your read model can be a flat denormalised thing that answers “every unshipped order from this week, most valuable first” in one query, and neither compromises for the other.&lt;/p&gt;

&lt;p&gt;The benefit is particularly felt in high-performance applications. Since read and write “stacks” are now separated, they can now grow and &lt;em&gt;scale&lt;/em&gt; independently. You can go from naive approaches, like having denormalized schema on reads to reduce the need of join oprations, to advenced approaches that involve even using &lt;strong&gt;multiple&lt;/strong&gt; data stores. Which means, storing models in different databases, or with even different technologies (ex., using a Redis cache for read operations, with faster data access).&lt;/p&gt;

&lt;p&gt;On the other side, keep in mind that like any pattern, CQRS is useful in some places, but not in others. Many systems do fit a CRUD mental model, and so should be done in that style. CQRS is a significant mental leap for all concerned, so shouldn’t be tackled unless the benefit is worth the jump. It could (and should) only be used on specific portions of a system (a BoundedContext in DDD) and not the system as a whole.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-two-event-sourcing-and-the-state-you-never-stored&quot;&gt;Step two: event sourcing, and the state you never stored&lt;/h2&gt;

&lt;p&gt;Event Sourcing answers a different question altogether: &lt;em&gt;what do we store?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Worth saying once before going into the details, since it confused me for weeks: &lt;strong&gt;CQRS and Event Sourcing are two separate patterns&lt;/strong&gt;, and the dependency runs one way only. Event sourcing effectively forces CQRS on you (for reasons we’ll get to). Most articles present them as one package.&lt;/p&gt;

&lt;p&gt;So let’s get back to that; &lt;strong&gt;Event Sourcing&lt;/strong&gt; is an architectural design pattern, based on a simple concept: determine &lt;em&gt;application state from a sequence of events&lt;/em&gt;. Not rows holding current values, but the ordered list of everything that ever happened. Instead of a row that says &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status = SHIPPED&lt;/code&gt;, you store &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OrderPlaced&lt;/code&gt;, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OrderDeliveryAddressChanged&lt;/code&gt;, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OrderShipped&lt;/code&gt;. Current state isn’t stored at all: you get it by replaying the list.&lt;/p&gt;

&lt;p&gt;My first reaction was suspicion, and I don’t think that’s unusual. But then you start noticing how many domains are &lt;em&gt;already&lt;/em&gt; shaped like this. A package moving through a logistics network is a sequence of events. A version control repository is a sequence of commits, and Git reconstructs your working tree by replaying them. Your relational database’s own write-ahead log works on the same principle, quietly, underneath the very system you were about to call “the normal way”.&lt;/p&gt;

&lt;p&gt;The reason to want this is that &lt;strong&gt;the history stops being something you have to build as a feature&lt;/strong&gt;. If somebody in your domain regularly asks “who changed this, when, and what did it look like before?”, event sourcing answers it for free, and a classic architecture answers it with a hand-written audit table that is always slightly out of date. That’s why it shows up in payments, insurance, betting and logistics, where the past is not a nice extra but the thing being regulated. And if nobody in your domain ever asks about the past, you’re paying for a feature nobody uses.&lt;/p&gt;

&lt;p&gt;Two rules come with that definition. An &lt;strong&gt;event is something that happened, never something that should happen&lt;/strong&gt;: that one is a &lt;strong&gt;command&lt;/strong&gt;, and commands can be rejected, while events can’t be. So you write them in the past tense, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OrderPlaced&lt;/code&gt; and not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PlaceOrder&lt;/code&gt;, using the vocabulary of the domain rather than of the database. And you &lt;strong&gt;never delete them&lt;/strong&gt;: removing something is itself an event appended to the stream, because the deletion is a fact like any other (with some exceptions, like GDPR; but let’s skirt around that, since we risk going off-topic).&lt;/p&gt;

&lt;p&gt;There aren’t many moving parts: the &lt;strong&gt;application&lt;/strong&gt; produces events, an &lt;strong&gt;event queue&lt;/strong&gt; (usually represented by a message broker) carries them, &lt;strong&gt;event handlers&lt;/strong&gt; react to them, doing the actual business logic, and the &lt;strong&gt;event store&lt;/strong&gt; keeps them durably. The ordered sequence flowing through all of it is the &lt;strong&gt;event stream&lt;/strong&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-08-21-from-cqrs-to-event-driven-architecture/03-event-sourcing.png&quot; alt=&quot;Event sourcing building blocks: the application, an event queue carrying the order events, event handlers, and the append-only event store&quot; /&gt;
  &lt;figcaption&gt;The application appends facts, handlers react, the store keeps everything forever&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;why-the-two-patterns-end-up-together&quot;&gt;Why the two patterns end up together&lt;/h3&gt;

&lt;p&gt;Storing facts instead of state raises an obvious question. How do you query any of this?&lt;/p&gt;

&lt;p&gt;It’s often said that event stores have terrible query performance, and that’s not quite right. An event store is excellent at the one question it exists to answer: “give me every event for aggregate 42, in order”. That’s an indexed range scan, and it’s fast. What it cannot do is answer &lt;em&gt;any&lt;/em&gt; question. “Every unshipped order from this week, most valuable first” spans thousands of streams, and no index makes replaying all of them viable.&lt;/p&gt;

&lt;p&gt;So the solution here is to build the answer in advance. An event handler subscribes to the stream and &lt;strong&gt;projects&lt;/strong&gt; it into a read model shaped like the question, and if you can build one projection (or &lt;em&gt;snapshot&lt;/em&gt;) you can build several: one for the dashboard, one for the search box, another one holding last week’s state if somebody asks for it. The application queries those, never the raw log.&lt;/p&gt;

&lt;p&gt;And that is CQRS, reached by necessity rather than by choice. Once your source of truth is an event stream, a separate read model isn’t an option you evaluate, it’s the only way to serve a query at all. This is why the two are almost always taught as one thing: the expensive pattern cannot work without the cheap one, so anybody explaining event sourcing has to explain CQRS on the way. What gets lost is that the reverse isn’t true.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-08-21-from-cqrs-to-event-driven-architecture/04-es-cqrs.png&quot; alt=&quot;Event-sourced CQRS: the order command service publishes to an event queue, which feeds the append-only event store and a projection handler, and the handler projects the stream into read models the order query service reads&quot; /&gt;
  &lt;figcaption&gt;The same split as before, with considerably more machinery&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;What you buy with all that machinery is the missing history crack, closed completely. You can rebuild any state at any time, which means “what did this look like on the 3rd of March?” stops being a research project. Another interesting consequence regards also &lt;em&gt;debugging&lt;/em&gt;: if a user hits a bug that only shows up after a specific sequence of operations, you replay their exact stream into a test environment and watch it happen, instead of guessing.&lt;/p&gt;

&lt;h3 id=&quot;the-reality-check&quot;&gt;The reality check&lt;/h3&gt;

&lt;p&gt;At this point I had a decent map and no idea whether anyone actually lived there. So I went looking for people who had actually built these systems, rather than people explaining them, and the picture is clearly divided.&lt;/p&gt;

&lt;p&gt;It pays off in some places. The creator of Axon Framework (a, probably “the”, state-of-the-art framework that allows to apply CQRS + Event Sourcing in Java), in &lt;a href=&quot;https://www.reddit.com/r/java/comments/6znmfi/anyone_using_axon_or_cqrs_event_sourcing_in_real&quot;&gt;a thread about real-world adoption&lt;/a&gt;, lists banks running core payment systems on it, airport management systems processing radar data, and the betting industry, &lt;em&gt;“because of the strict auditing requirements and high value of past events.”&lt;/em&gt; Someone rebuilding tracking for a large logistics firm put it more sharply: the traditional model, &lt;em&gt;“CRUD, locks, relational DB, is the one that ADDS a lot of complexity here, not the event sourcing stack we chose.”&lt;/em&gt; That’s the strongest case for the pattern. Not that it’s powerful, but that in the right domain it’s &lt;em&gt;simpler than the alternative&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And it could be a disaster in others. A team processing 40,000 IoT messages a second, exactly the “high throughput” territory you’d expect to be a good fit, tried it and scrapped it: it &lt;em&gt;“adds massive amounts of complexity, and what would be a simple problem to solve in standard architecture becomes a whole sprint for your team.”&lt;/em&gt; Their conclusion was to stick with a traditional architecture until it can’t cut it, then refactor, rather than choosing it from the start.&lt;/p&gt;

&lt;p&gt;So the thing that decides isn’t throughput, it’s whether the &lt;em&gt;domain&lt;/em&gt; really is a sequence of facts that somebody needs to audit. High volume on its own buys you nothing here. It just makes the overhead more expensive.&lt;/p&gt;

&lt;p&gt;My take, after all that reading: full event sourcing across a whole domain is right for a narrow slice of systems, and for most applications adopting it wholesale costs more than it gives back.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;ideas&lt;/em&gt; underneath were still right, though, even where the full pattern was wrong. Separate the intent from the execution, write down what the user asked for before you do it, treat a request as a durable fact instead of an open connection. All of that seemed valuable at a much smaller scale, in one endpoint or one module. Which left me with a question I felt slightly stupid asking: does the small version have a name, or was I just describing a queue with extra steps?&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-three-asynchronous-request-reply-the-same-idea-one-endpoint-wide&quot;&gt;Step three, Asynchronous Request-Reply: the same idea, one endpoint wide&lt;/h2&gt;

&lt;p&gt;It does have a name: &lt;strong&gt;Asynchronous Request-Reply&lt;/strong&gt;, and it’s about the &lt;em&gt;interaction&lt;/em&gt; between client and server.&lt;/p&gt;

&lt;p&gt;A client sends a request whose processing is slow, or depends on downstream systems you don’t control. The classic answer is to hold the HTTP connection open and hope. The pattern’s answer is to stop doing that. The server accepts the request, returns &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;202 Accepted&lt;/code&gt; immediately with an identifier (a job ID), and the client either polls that identifier on a status endpoint, or gets notified later via webhook or WebSocket. When the work is completed, the status endpoint will return an HTTP &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;302 (Found)&lt;/code&gt; response with a resource URL that the client can be redirected to.&lt;/p&gt;

&lt;p&gt;Underneath, the incoming request becomes an explicit &lt;strong&gt;command&lt;/strong&gt; object, something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ProcessPaymentCommand&lt;/code&gt;, and goes on a queue. Background workers pull commands off and execute them.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-08-21-from-cqrs-to-event-driven-architecture/07-async-request-reply.png&quot; alt=&quot;Asynchronous Request-Reply: the client posts an order, the API persists a PENDING job and answers 202 with a jobId, a worker claims and creates the order, and the client polls a status endpoint that eventually answers 302 with the order url&quot; /&gt;
  &lt;figcaption&gt;Accept now, execute later, let the client come back for the result&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Now look at what it buys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retries come for free&lt;/strong&gt;, because the failure branch isn’t error handling bolted on afterwards, it’s the same state machine as the success branch. When the external API is down, the command sits in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PENDING&lt;/code&gt; and gets tried again later. Your user doesn’t get a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;500&lt;/code&gt; for something that was never their fault.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You also get load smoothing&lt;/strong&gt;. Traffic spike, flash sale, whatever: the API’s job is now to write a row, which it does very fast. Ingestion runs at spike speed, execution runs at whatever pace your workers can sustain, and the queue absorbs the difference between the two.&lt;/p&gt;

&lt;p&gt;And almost by accident &lt;strong&gt;you end up with an audit trail of intent&lt;/strong&gt;. That stored command is a record of &lt;em&gt;what the user wanted&lt;/em&gt;, so if a bug corrupts your final state, you still have the original request sitting there and you can replay it. It feels like an event sourcing benefit, and it came from a single table.&lt;/p&gt;

&lt;p&gt;And now the cost, because this step is cheap but not free. &lt;strong&gt;Your client needs somewhere to collect the result&lt;/strong&gt;, which means polling endpoints or webhook delivery, infrastructure you didn’t have before. And the awkward one: &lt;strong&gt;business errors now surface &lt;em&gt;after&lt;/em&gt; you’ve answered &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;202&lt;/code&gt;&lt;/strong&gt;. You can no longer tell the user “your card was declined” in the response to their own request. That has to travel back through a notification, an email, or a status endpoint, and designing that path properly is usually more work than the worker loop itself. Anything the user must know immediately has to stay in the synchronous validation step, which puts real pressure on where you draw that line.&lt;/p&gt;

&lt;p&gt;Still, this is the shape I was searching for from the start, and the one my colleague had been describing all along: intent separated from execution, at exactly one boundary, with no framework and no event store behind it. &lt;strong&gt;One table and a loop&lt;/strong&gt;. This article could stop here, but since we are engineers, we noticed that something else can be improved. Let’s get deeper into the rabbit hole.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-four-the-dual-write-and-the-transactional-outbox&quot;&gt;Step four: the dual write, and the Transactional Outbox&lt;/h2&gt;

&lt;p&gt;Step three left the worker doing its job against a single database, which is a comfortable place to be: one database means transactions. Now give that worker the one extra responsibility every real system eventually needs. Once it has saved the order, it also has to tell somebody else, so it publishes a message to a broker.&lt;/p&gt;

&lt;p&gt;That’s two writes, to two different systems, with no transaction covering both, and it has a name: the &lt;strong&gt;dual-write problem&lt;/strong&gt;. The database transaction can commit while the publish fails, and you get an order that nobody downstream ever hears about. Or the publish succeeds and the transaction rolls back, and the rest of the company reacts to an order that doesn’t exist. You can wrap the database write in a transaction. You cannot enrol the broker in it. So the two can disagree, and when they do, nothing tells you.&lt;/p&gt;

&lt;p&gt;The usual mitigations, retries and try/catch and publishing only after the commit, all narrow the window without closing it. The process can die &lt;em&gt;inside&lt;/em&gt; the window. There is no arrangement of two independent writes that is atomic. The only real fix is to stop having two writes.&lt;/p&gt;

&lt;p&gt;Meet the &lt;strong&gt;Transactional Outbox&lt;/strong&gt; pattern: instead of publishing to the broker, insert the message into an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;outbox&lt;/code&gt; table &lt;strong&gt;in the same database transaction&lt;/strong&gt; as your business data. Then a separate process called a &lt;strong&gt;relay&lt;/strong&gt; reads the outbox and publishes onward.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;on handling a command, inside ONE transaction:
    save(order)                                  # business state
    insert(outbox, OrderPlaced(order.id, ...))   # the message, same transaction
    commit                                       # both, or neither

relay process (separate, runs continuously):
    for row in outbox where not published:
        publish(broker, row.payload)
        mark(row, published)                     # at-least-once: consumers must be idempotent
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-08-21-from-cqrs-to-event-driven-architecture/05-outbox.png&quot; alt=&quot;The transactional outbox: the ORDERS and OUTBOX tables written inside a single database transaction, with a relay reading the outbox and publishing to the broker&quot; /&gt;
  &lt;figcaption&gt;Business data and message in one commit, or neither&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The relay itself can be boring. In the Spring world could be a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Scheduled&lt;/code&gt; job that reads the unpublished rows and hands them to a message broker, like Kafka.&lt;/p&gt;

&lt;p&gt;Now look at that relay loop again, because it doesn’t fully escape the problem either. It publishes, then it marks the row as published, and those two steps aren’t atomic: a crash in between sends the same message twice. The outbox hasn’t deleted the risk, it has &lt;em&gt;moved&lt;/em&gt; it, from “the message might be lost” to “the message might arrive twice”. That trade is the point of the exercise, because &lt;strong&gt;a lost message is unrecoverable while a duplicated one is an engineering problem with known solutions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s called &lt;strong&gt;at-least-once&lt;/strong&gt; delivery: every message arrives, some arrive more than once, and the responsibility shifts to whoever receives them. Those &lt;strong&gt;receivers have to be idempotent&lt;/strong&gt;, meaning that handling the same message twice leaves the system in the same state as handling it once. In practice that’s an identifier carried in the message (the order ID is usually sitting right there) plus a uniqueness constraint or a “have I already processed this one?” check that turns the second attempt into a no-op. Once messages are moving between systems, idempotency stops being a detail and becomes a house rule.&lt;/p&gt;

&lt;h3 id=&quot;step-four-and-a-half-joining-the-dots&quot;&gt;Step four and a half: joining the dots&lt;/h3&gt;

&lt;p&gt;It’s worth stopping here to look at a small variation on the outbox, because this is where everything links back to the earlier steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen To Yourself&lt;/strong&gt; is a variation on the Transactional Outbox where you simply invert the order. Instead of writing your state and an outbox row together, you publish the event to the broker &lt;em&gt;first&lt;/em&gt;, and then let your own service consume its own event and update its local state from it. There is only one write, so there is nothing left to disagree with, and the broker becomes the source of truth.&lt;/p&gt;

&lt;p&gt;Sound familiar? &lt;strong&gt;It’s basically step three, Asynchronous Request-Reply, implemented with events instead of a command table&lt;/strong&gt;. The endpoint accepts the request, publishes the fact, answers &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;202&lt;/code&gt;, and the real work happens later when your own consumer picks the event up. Same separation between intent and execution, different transport. The price is the one from step two: your own state is now eventually consistent with the event you have just published, so anything reading immediately afterwards can still see the previous value, and you have to decide whether your domain tolerates that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Event Sourcing&lt;/strong&gt; closes the circle from the other side. If the event you publish is also the event you store, then &lt;strong&gt;the outbox table and the event store stop being two different things&lt;/strong&gt;. There is no second write to disagree with, because the write &lt;em&gt;is&lt;/em&gt; the event. The dual-write problem doesn’t get solved so much as it stops existing, and that was part of what step two had been selling all along.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;step-five-zoom-out-and-its-event-driven-architecture&quot;&gt;Step five: zoom out, and it’s Event-Driven Architecture&lt;/h2&gt;

&lt;p&gt;The last step doesn’t need any new machinery, only a change of scope.&lt;/p&gt;

&lt;p&gt;Everything so far has been inside one application. The outbox row was published to a broker so &lt;em&gt;your own&lt;/em&gt; background worker could pick it up. Now stop thinking about your service and think about the whole company.&lt;/p&gt;

&lt;p&gt;That same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OrderPlaced&lt;/code&gt; event, published to a shared enterprise broker like Kafka, isn’t just yours anymore. Billing consumes it to issue an invoice. Inventory consumes it to decrement stock. Shipping consumes it to create a label. Analytics consumes it because analytics consumes everything.&lt;/p&gt;

&lt;p&gt;None of those services called your API. You didn’t call theirs. You don’t know they exist, and, this is the important part, you don’t have to change your code when a fourth one shows up next quarter. You published a fact, and whoever cares about it, cares.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-08-21-from-cqrs-to-event-driven-architecture/06-eda.png&quot; alt=&quot;Event-driven architecture: the order service relay publishes OrderPlaced to a Kafka topic, consumed by billing, inventory, shipping and analytics, each with its own database&quot; /&gt;
  &lt;figcaption&gt;The outbox pattern, with a wider audience&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;That’s &lt;strong&gt;Event-Driven Architecture&lt;/strong&gt;, and it’s just the outbox pattern with a wider audience.&lt;/p&gt;

&lt;p&gt;That also changes what the outbox is for; it’s the safety mechanism that lets you adopt EDA without losing data. Without it, every service publishing to the shared broker has an unguarded dual write, and your beautifully decoupled architecture is silently losing messages at every node.&lt;/p&gt;

&lt;p&gt;On the other side, keep in mind that even that “globally decoupled” application comes with a cost. &lt;strong&gt;You give up global ordering&lt;/strong&gt;, so events about the same entity can reach a consumer out of sequence. &lt;strong&gt;You inherit at-least-once delivery organisation-wide&lt;/strong&gt;, so every consumer needs the idempotency we keep coming back to. &lt;strong&gt;Debugging stops being a stack trace&lt;/strong&gt; and becomes a correlation ID chased across five services’ logs. And the subtle one: your event schema becomes public API. A REST endpoint has known callers you can go and talk to. The coupling didn’t disappear, it moved into the payload, where it’s harder to see.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;what-i-actually-took-away&quot;&gt;What I actually took away&lt;/h2&gt;

&lt;p&gt;I set out to fill in a missing piece, and ended up tracing, accidentally and roughly backwards, the path the industry took to arrive at event-driven architecture. What a journey!&lt;/p&gt;

&lt;p&gt;In hindsight, the first thing I can do is go back to those two suggestions and finally answer them.&lt;/p&gt;

&lt;p&gt;To my manager: it’s clear now what he meant. He was pointing at event sourcing as a way to get proper auditing of every change to the entity, for free. On that specific system, though, I still don’t think it would have been the right fit. The audit trail would have been a real gain, but that domain was complex, and complex in the way that resists being modelled as a list of events.&lt;/p&gt;

&lt;p&gt;To my colleague: it’s clear now that he was pointing at asynchronous request-reply, or some variation of it. Here I partly agree. His suggestion was a general one, move the whole architecture in that direction, and that part I don’t buy: it doesn’t remove the complexity, it relocates it. The same code lives somewhere else, and the request that used to fail with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;500&lt;/code&gt; now gets a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;202&lt;/code&gt; and fails later, out of sight.&lt;/p&gt;

&lt;p&gt;I said &lt;em&gt;partly&lt;/em&gt;, though, because there were a few contexts where a system like that made a lot of sense, and we built one without knowing the pattern had a name. The customer needed to submit a large batch of requests at once, each one potentially slow, and was fine with them being handled asynchronously. Perfect fit, and we ended up with asynchronous request-reply by accident.&lt;/p&gt;

&lt;p&gt;What I concretely take away is to use these ideas &lt;em&gt;surgically&lt;/em&gt;. Weigh the cost every time, and be happy to know one pattern more than you did last time. And these days, when somebody drops “we could use events here” into a brainstorming session, I at least know now which of the five things they’re pointing at.&lt;/p&gt;

&lt;h2 id=&quot;sources&quot;&gt;Sources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=A0goyZ9F4bg&quot;&gt;CQRS and Event Sourcing&lt;/a&gt;, Michael Ploed at SpringOne2GX 2015. The walkthrough I followed for steps one and two.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gregfyoung.wordpress.com/2012/09/09/cqrs-is-not-an-architecture&quot;&gt;CQRS is not an Architecture&lt;/a&gt;, Greg Young, 2012.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://martinfowler.com/bliki/CQRS.html&quot;&gt;CQRS&lt;/a&gt;, Martin Fowler, 2011.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://martinfowler.com/articles/201701-event-driven.html&quot;&gt;What do you mean by “Event-Driven”?&lt;/a&gt;, Martin Fowler, 2017.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://martinfowler.com/eaaDev/EventSourcing.html&quot;&gt;Event Sourcing&lt;/a&gt;, Martin Fowler, 2005. The original write-up of the pattern.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/java/comments/6znmfi/anyone_using_axon_or_cqrs_event_sourcing_in_real&quot;&gt;Anyone using Axon or CQRS/event sourcing in real life?&lt;/a&gt; Reddit thread with a comment from an Axon maintainer, quoted in the reality check chapter, 2017.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/architecture/patterns/asynchronous-request-reply&quot;&gt;Asynchronous Request-Reply&lt;/a&gt;, Azure Architecture Center, 2026.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://microservices.io/patterns/data/transactional-outbox.html&quot;&gt;Transactional Outbox&lt;/a&gt;, Chris Richardson, 2019.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/event-driven&quot;&gt;Event-driven architecture style&lt;/a&gt;, Azure Architecture Center, 2026.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/ChintaHari/springboot-transactional-outbox-pattern&quot;&gt;A Spring Boot outbox implementation&lt;/a&gt;, if you want to experiment with a working code example.&lt;/li&gt;
  &lt;li&gt;The header image is &lt;a href=&quot;https://www.nasa.gov/image-article/webb-opens-treasure-chest&quot;&gt;Webb Opens Treasure Chest&lt;/a&gt;, from NASA.&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Fri, 21 Aug 2026 00:00:00 +0000</pubDate>
        <link>https://riccardomaldini.it/blog/from-cqrs-to-event-driven-architecture/</link>
        <guid isPermaLink="true">https://riccardomaldini.it/blog/from-cqrs-to-event-driven-architecture/</guid>
        
        <category>architecture</category>
        
        <category>cqrs</category>
        
        <category>event-sourcing</category>
        
        <category>eda</category>
        
        <category>backend</category>
        
      </item>
    
      <item>
        <title>The Canestreet Website: From a Simple Showcase to the Tournament&apos;s Core ERP</title>
        <description>&lt;p&gt;If you type &lt;a href=&quot;http://canestreet.it&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;canestreet.it&lt;/code&gt;&lt;/a&gt; into your browser’s search bar, you’ll land on a sleek, modern website. The subject is instantly clear: a summer basketball tournament. The splash screen greets you with action shots from the past editions, and a quick glance reveals everything you’d expect from a sports hub. Team registration, live results, official rules, and a curated about section.&lt;/p&gt;

&lt;p&gt;It looks exactly like what a professional tournament needs. And, as you might have guessed, it’s a website I built 😏&lt;/p&gt;

&lt;p&gt;Canestreet is the name of a small summer basketball 3x3 tournament we host every year in my hometown Jesi, Italy. We’ve been running it for eight years now. What started out as a casual joke among friends has evolved into a highly anticipated summer event. And with that growth came a mountain of logistical headaches.&lt;/p&gt;

&lt;p&gt;This article isn’t about the tournament itself (there is &lt;a href=&quot;https://riccardomaldini.it/blog/canestreet-3x3&quot;&gt;a whole other piece on that&lt;/a&gt;). Instead, this is a spin-off story. It’s a deep dive into a tool I briefly mentioned in the main article, but one that deserves its own spotlight: &lt;strong&gt;the Canestreet digital ecosystem&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-good-ol-times-rough-basketball-and-small-needs&quot;&gt;The Good Ol’ Times: Rough Basketball and Small Needs&lt;/h2&gt;

&lt;p&gt;Canestreet was born in 2018, primarily as a fun way to break up the sweltering Italian summer heat. A few friends and I set up a modest event on a small basketball court owned by a local church.&lt;/p&gt;

&lt;p&gt;In those early days, we didn’t need much technology. Word of mouth was our primary marketing engine. We only needed eight teams to make the tournament viable, and we reached almost all the players through friend-of-a-friend networks.&lt;/p&gt;

&lt;p&gt;To give the event a slight digital footprint, we created &lt;a href=&quot;https://www.instagram.com/canestreet3x3&quot;&gt;an Instagram page&lt;/a&gt;. It was mostly fueled by memes, funny team descriptions, event announcements, and stories updating the match results. It was the perfect, low-friction hotspot to keep players engaged.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-05-07-canestreet-website/early-instagram-post.png&quot; alt=&quot;Instagram post from the early days&quot; /&gt;
  &lt;figcaption&gt;One of the Instagram Posts from the early days. What a simpatico umorista I was&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We kept replicating the model year after year, even when we upgraded to a much larger playground. For nearly five years, Instagram remained our de facto point of contact with the outside world.&lt;/p&gt;

&lt;p&gt;On the organizational side, however, we needed a bit more structure to track rounds, points, and final brackets. Pen and paper quickly became a nightmare for a tournament with more than 50 players. Our grand upgrade? &lt;strong&gt;A massive web of Google Sheets&lt;/strong&gt;. At the time, it was a medium perfectly tailored to the size of our needs. But as the tournament continued to balloon, we began craving something that felt a bit more professional.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-05-07-canestreet-website/standing-gsheet.png&quot; alt=&quot;The Google Sheet we used in the first editions&quot; /&gt;
  &lt;figcaption&gt;The Google Sheet we used in the first editions. The whole tournament was based on that.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-first-website-the-showcase-we-needed&quot;&gt;The First Website: The Showcase We Needed&lt;/h2&gt;

&lt;p&gt;We managed to run the tournament for three consecutive years before Covid brought everything to a grinding halt. That forced hiatus gave us time to rethink our next steps. More importantly for this story, it handed me, as a programmer, an abundance of free time to invest in whatever random side project crossed my mind.&lt;/p&gt;

&lt;p&gt;That lockdown boredom was the exact cradle where &lt;strong&gt;the first version of the Canestreet website was born&lt;/strong&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-05-07-canestreet-website/old-website.png&quot; alt=&quot;Homepage of the old website. Slay&quot; /&gt;
  &lt;figcaption&gt;Homepage of the old website. Slay&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I wanted to give the tournament a proper virtual home. Instagram would still be our primary way to engage users, but like any growing entity, we needed a dedicated space on the web to showcase who we were outside of social media silos. Let’s be honest: an Instagram page isn’t the best business card when you’re pitching to a serious potential corporate sponsor.&lt;/p&gt;

&lt;p&gt;Like any good engineer, I started by collecting requirements. What does a tournament website actually need?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;A &lt;strong&gt;Homepage&lt;/strong&gt;: A visual showcase with photos and a compelling description of the event.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Contacts&lt;/strong&gt;: Where to find us, and an easy way for local businesses to reach the main organizer for sponsorships.&lt;/li&gt;
  &lt;li&gt;A &lt;strong&gt;News&lt;/strong&gt; Section: A place for updates that could be easily shared via a single permalink.&lt;/li&gt;
  &lt;li&gt;Some &lt;strong&gt;nice-to-have&lt;/strong&gt;: A registration portal, an interactive rules page, and live match results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reconciling all of these features was a massive challenge for me at the time because my web development experience was pretty limited. I had experimented with a few small projects using Angular and headless CMSs (see &lt;a href=&quot;https://riccardomaldini.it/blog/covidanalysis&quot;&gt;the origins of CovidAnalysis&lt;/a&gt; and &lt;a href=&quot;https://riccardomaldini.it/blog/my-website&quot;&gt;my blog&lt;/a&gt; for more funny stories!), but I had never built a comprehensive, production-grade web application.&lt;/p&gt;

&lt;p&gt;Following the breadcrumbs of convenience led me straight to a precise technology. I had to strike a deal with the framework I disliked the most—the monster that powers half the web, yet is widely mocked by developers everywhere.&lt;/p&gt;

&lt;p&gt;Yes, I’m talking about &lt;strong&gt;WordPress&lt;/strong&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-05-07-canestreet-website/wordpress-scientist.png&quot; alt=&quot;That&apos;s how I&apos;m imaging me while working on a Wordpress projects (credits: Nano Banana)&quot; /&gt;
  &lt;figcaption&gt;That&apos;s how I imagine me working on a Wordpress project (credits: Nano Banana)&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I genuinely dislike WordPress. It’s a CMS born for simple blogging that has been aggressively bent, twisted, and forced by themes and plugins to act as a solution for every website requirement under the sun. It’s flexible, sure, but forcing a tool so far outside its original scope always felt fundamentally wrong to me.&lt;/p&gt;

&lt;p&gt;Yet, because it was an industry standard, it was the most pragmatic place to start experimenting. I knew that learning my way around a tool used by half the internet would eventually be a useful skill to have in my back pocket.&lt;/p&gt;

&lt;p&gt;So, I held my nose, bought the domain &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;thecanestreet.it&lt;/code&gt;, and linked it to a hosted WordPress instance. That’s the resulting stack I came out with:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Component&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Technology&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Cost&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Why We Used It&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Framework&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;WordPress&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Free (OSS)&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;The industry standard for quickly building functional web spaces.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Page Builder&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Elementor Plugin&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Free Tier&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Allowed drag-and-drop page creation without deep frontend experience. I didn’t want to invest too much time on that.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Sports Engine&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;WP Club Manager&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Free&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;A pre-built plugin used to handle matches, players, and standings.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Registrations&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Google Forms&lt;/strong&gt; (iFrame)&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;€0&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Embedded directly into the page to collect user sign-up data.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Hosting &amp;amp; Domain&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Self-Hosted&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;€25/year&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Necessary to get a custom domain, Linux hosting and the WordPress database.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Quite cheap, but not free as you can see. I spent a month tinkering with the Elementor plugin, building out the pages we needed one by one. I built the homepage, added an “About” and “Contact” page, and even embedded a PDF viewer to display the official FIBA 3x3 rules. Finally, I spun up a news section utilizing the native WordPress blog architecture.&lt;/p&gt;

&lt;p&gt;This first iteration actually survived for four years. Over time, I expanded its capabilities by adding a third-party plugin for live sports casting and embedding a basic Google Form iFrame to handle player sign-ups.&lt;/p&gt;

&lt;p&gt;The solution was minimal, but it got the job done. The glaring downside? &lt;strong&gt;The sheer amount of manual data manipulation required&lt;/strong&gt; behind the scenes. The pain points were two in particular:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Registrations&lt;/strong&gt;: Teams submitted forms, but everything after that was manual labor. We had to manually export the data from Google Sheets, email the team captain to confirm, and spend days chasing people down to validate physical sports insurance and federation certificates.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Live Scoring&lt;/strong&gt;: The sports-casting plugin required an immense amount of configuration. Updating scores during a fast-paced game was tedious, and getting brackets to align properly with complex FIBA tie-breaker rules required constant manual intervention.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every time a developer handles a repetitive task that could easily be automated, an angel loses its wings 🥴 I knew I needed a change.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-second-website-the-ai-breakthrough&quot;&gt;The Second Website: The AI Breakthrough&lt;/h2&gt;

&lt;p&gt;Then came the dawn of 2026, and everything shifted.&lt;/p&gt;

&lt;p&gt;Over the previous several months, generative AI tools designed specifically for coding evolved from amusing novelties into powerhouse assistants. The launch of advanced coding models completely revolutionized our day-to-day engineering workflows.&lt;/p&gt;

&lt;p&gt;These tools successfully took over the heavy lifting of concrete syntax and boilerplate implementation, freeing us up to focus on high-level architecture, system design, and product specifications. I was fortunate enough to be working at a company that actively encourages using tools like &lt;strong&gt;Claude Code&lt;/strong&gt;, even subsidizing licenses for our personal development. It’s a massive win-win: continuous self-training for the employees, and unlimited building potential for the engineers.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-05-07-canestreet-website/walk-with-copilot.png&quot; alt=&quot;She just wanted an ice cream&quot; /&gt;
  &lt;figcaption&gt;She just wanted an ice cream&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I decided to burn down the old WordPress site and build a custom application from scratch. I didn’t want just a public showcase anymore; I wanted a fully bespoke Enterprise Resource Planning (ERP) system tailored perfectly to the chaos of a 3x3 basketball tournament.&lt;/p&gt;

&lt;p&gt;I brainstormed architectures, aiming for modern tech stack performance while keeping hosting costs at a grand total of zero, or near-zero Euros per year.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Component&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Technology&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Cost&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Why We Used It&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Framework&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Next.js&lt;/strong&gt; (React)&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;€0&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Single codebase for both frontend UI and API backend routes.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Hosting&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Vercel&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;€0&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Seamless deployment and incredible loading speeds.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Supabase&lt;/strong&gt; (PostgreSQL)&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;€0&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Relational powerhouse on a generous free tier.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Assistant&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Sponsored&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Handled the heavy boilerplate lifting in seconds.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;Domain&lt;/strong&gt;&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;&lt;strong&gt;canestreet.it&lt;/strong&gt; (GoDaddy)&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;€10/year&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Sticked to the provider since i have previous sites here (using other ones i could have expeded even less).&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;I had a functional, bare-bones prototype running in less than an afternoon. I was completely stunned. In just a few hours, I managed to build the core repository skeleton alongside clean, responsive designs for the home, about, contact, and rules sections.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-05-07-canestreet-website/website-pc.png&quot; alt=&quot;Homepage of the old new website&quot; /&gt;
  &lt;figcaption&gt;Homepage of the new website. Type shit&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Galvanized by this newfound velocity, I kept iterating. Within a week, the public-facing application was complete. The following week, I turned my attention to building a custom administrative back-office dashboard.&lt;/p&gt;

&lt;p&gt;Built entirely around the real pain points we gathered over seven years of running tournaments, the new system seamlessly automates our entire operation:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Automated Registrations&lt;/strong&gt;: Teams register directly via custom web forms. The app parses the data, alerts the staff, and fires off beautiful automated confirmation emails once approved.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;FIBA-Compliant Live Engine&lt;/strong&gt;: No more fighting generic sports plugins. Our backend automatically takes registered teams, generates round-robin groups, dynamically calculates standings based on complex FIBA 3x3 tie-breaker rules, and populates the playoff brackets in real-time for spectators.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Dynamic Sponsor Management&lt;/strong&gt;: Sponsors can be added, categorized (Gold, Silver, Technical), and updated instantly via the dashboard, automatically rendering them across the platform.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;The Court Jumbotron&lt;/strong&gt;: Solving an old logistical problem, I built a dedicated “Showcase View” meant to be projected on TVs or laptops around the courts. It loops through real-time scores, upcoming match timetables, and active sponsor loops without needing manual refreshes.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Three-Point Contest Module&lt;/strong&gt;: A streamlined micro-dashboard to register players for our annual shootout, input their scores live, and broadcast a real-time leaderboard to the crowd.&lt;/li&gt;
&lt;/ul&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-05-07-canestreet-website/mobile-screens.jpg&quot; alt=&quot;Some screenshots from the mobile version of the website&quot; /&gt;
  &lt;figcaption&gt;Some screenshots from the mobile version of the website. Fully responsive, 100% compatible with every device.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;conclusion-and-takeaways&quot;&gt;Conclusion and Takeaways&lt;/h2&gt;

&lt;p&gt;Looking back at the trajectory of this project, it is genuinely incredible what a single independent developer can achieve with modern tools. Building a system of this complexity in my limited free time &lt;strong&gt;would have easily taken me several months of grueling weekend work in the past&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Working with these AI tools as a developer can be overwhelming. In particular, when they were initially introduced, &lt;strong&gt;the experience was almost addicting, like gambling&lt;/strong&gt;. You prompt your intentions, achieve your result, and cannot wait for your PC-shaped slot machine to let you insert the coin for the next round.&lt;/p&gt;

&lt;p&gt;It is important that you do not fall into that rabbit hole and that, with time and iteration, you learn how to steer this technology. Having this power in your hands without proper discipline would allow the architecture to degenerate into an unmaintainable mess.&lt;/p&gt;

&lt;p&gt;Let’s remember that we are engineers at the end of the day. &lt;strong&gt;We are responsible for the codebase&lt;/strong&gt;, not Mr. Claudio. It is more critical than ever that we maintain absolute control over the code, thoroughly understand the output, and never go on full autopilot. Total reliance on automation is the easiest way to make a complex software project fail.&lt;/p&gt;

&lt;p&gt;Do you know what’s the best part of thiw project? &lt;strong&gt;The entire Canestreet platform is completely open-source and &lt;a href=&quot;https://github.com/maldins46/CanestreetWebsite&quot;&gt;available on my GitHub&lt;/a&gt;&lt;/strong&gt;! The tournament is my hobby, and this code is simply the engineering cherry on top. If you run a local sports tournament and want a tailored, automated management system to make your event shine, feel free to clone the repository. Just remember to give credit to your favourite developer from Jesi 🥰&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-05-07-canestreet-website/canestreet-github.png&quot; alt=&quot;The Canestreet Website Project on GitHub&quot; /&gt;
  &lt;figcaption&gt;The Canestreet Website Project on GitHub&lt;/figcaption&gt;
&lt;/figure&gt;
</description>
        <pubDate>Sun, 17 May 2026 00:00:00 +0000</pubDate>
        <link>https://riccardomaldini.it/blog/canestreet-website/</link>
        <guid isPermaLink="true">https://riccardomaldini.it/blog/canestreet-website/</guid>
        
        <category>projects</category>
        
        <category>basket</category>
        
        <category>react</category>
        
        <category>vercel</category>
        
      </item>
    
      <item>
        <title>GitFit: Because Your Morning Run Deserves a Green Square Too</title>
        <description>&lt;p&gt;What does your GitHub contribution graph say about you?&lt;/p&gt;

&lt;p&gt;If you’re like me, it says you write code. You commit features, fix bugs, refactor messy functions. Some weeks the graph lights up green. Other weeks? Dark, empty squares that whisper &lt;em&gt;“where were you?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But here’s the thing: those empty weeks? I wasn’t idle. I was running. Cycling. Pushing myself physically in ways that took just as much discipline—maybe more—than writing clean code. &lt;strong&gt;I was growing, just not in a way GitHub could see.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that bothered me more than it probably should.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-invisible-work&quot;&gt;The Invisible Work&lt;/h2&gt;

&lt;p&gt;Autumn, 2025. I’d just changed jobs, and like anyone in a period of transition, I was doing a lot of reflecting. &lt;em&gt;Am I growing? Am I better than I was last year?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer was yes—but not in the ways I expected. Sure, I was coding at work, building features, solving problems. But I’d also committed myself to something else: &lt;strong&gt;taking care of myself&lt;/strong&gt;. Morning runs tracked on my Pixel Watch 2. Gym sessions, strength training, actual consistent exercise for the first time in… well, let’s not count the years.&lt;/p&gt;

&lt;p&gt;I’d track everything with Fitbit—runs syncing over to Strava, gym sessions logged, movement counted. I’d watch the stats tick up, feel good about my progress. Then I’d check my GitHub profile and see… nothing. Just the commits from work.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-03-04-gitfit/contributions-before.png&quot; alt=&quot;My contribution graph before GitFit: lots of empty squares during my most active fitness months&quot; /&gt;
  &lt;figcaption&gt;GitHub contribution graph showing mostly empty weeks&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Here’s what I realized: as a developer, those green squares actually matter to me.&lt;/strong&gt; Not in a “gaming the system” way, but in a genuine “visible progress is motivating” way. When I see a solid week of commits, it feels like proof I showed up.&lt;/p&gt;

&lt;p&gt;So why shouldn’t my morning 5K count?&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-programmers-reward-problem&quot;&gt;The Programmer’s Reward Problem&lt;/h2&gt;

&lt;p&gt;Let me be honest about something: caring about your GitHub contribution graph is a little silly. It’s vanity metrics. It can be gamed. It doesn’t actually measure quality or impact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And yet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’re programmers. We respond to feedback loops. We like seeing things &lt;em&gt;work&lt;/em&gt;. When you write code, push a commit, and see that green square appear—there’s a tiny dopamine hit. “I did something today.”&lt;/p&gt;

&lt;p&gt;For months, I’d been doing &lt;em&gt;plenty&lt;/em&gt; of things. But GitHub didn’t know that. My contribution graph told a story of inconsistency, of sporadic effort. It didn’t show the 6 AM wake-ups, the kilometers logged, the discipline maintained.&lt;/p&gt;

&lt;p&gt;I wanted those green squares to tell a fuller story. &lt;strong&gt;Not just what code I wrote, but what kind of person I was becoming.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s when the idea clicked: &lt;em&gt;What if I could sync my fitness activities to GitHub?&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;gitfit-the-solution-i-didnt-know-i-needed&quot;&gt;GitFit: The Solution I Didn’t Know I Needed&lt;/h2&gt;

&lt;p&gt;The concept was surprisingly simple:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Fetch my latest activities from a fitness tracking API&lt;/li&gt;
  &lt;li&gt;Create commits in a private GitHub repository for each activity&lt;/li&gt;
  &lt;li&gt;Let those commits show up as green squares on my public contribution graph&lt;/li&gt;
  &lt;li&gt;Keep the actual workout data private (because nobody needs to see my pace per kilometer—trust me)&lt;/li&gt;
&lt;/ol&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-03-04-gitfit/architecture.png&quot; alt=&quot;How GitFit works: public repo runs the sync code, commits push to a private repo&quot; /&gt;
  &lt;figcaption&gt;Architecture diagram showing Strava API to GitHub via Actions&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I started with &lt;strong&gt;Strava as the first integration&lt;/strong&gt;—their API is well-documented and straightforward to work with. But the vision is bigger: eventually adding Fitbit, Google Fit, whatever system I’m using to track my gym sessions and runs.&lt;/p&gt;

&lt;p&gt;The beauty of this approach? &lt;strong&gt;My workout data stays completely private&lt;/strong&gt;, locked away in a repo only I can access. But the &lt;em&gt;fact&lt;/em&gt; that I worked out? That shows up. One green square per activity. Or two, or three, or five—depending on how far or long I went.&lt;/p&gt;

&lt;p&gt;I built the whole thing with Python and GitHub Actions. Every day at 6 AM UTC, a workflow spins up, checks Strava for new activities, and creates corresponding commits. The code lives in &lt;a href=&quot;https://github.com/maldins46/GitFit&quot;&gt;a public repo on GitHub&lt;/a&gt;—open source, because why not?&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2026-03-04-gitfit/activity-example.png&quot; alt=&quot;An evening run from February 2026, now immortalized as green squares&quot; /&gt;
  &lt;figcaption&gt;Example Strava activity that generated commits&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The setup takes maybe 10 minutes if you follow the README. You need:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;A Strava account (obviously)&lt;/li&gt;
  &lt;li&gt;Two GitHub repos: one public for the code, one private for the commits&lt;/li&gt;
  &lt;li&gt;A handful of API tokens and secrets&lt;/li&gt;
  &lt;li&gt;The willingness to enable “Show private contributions” on your GitHub profile&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And then? It just works. Every run, every ride—automated, synced, visible.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-bigger-picture&quot;&gt;The Bigger Picture&lt;/h2&gt;

&lt;p&gt;I know how this sounds. “You built an automation to make your GitHub graph prettier? Really?”&lt;/p&gt;

&lt;p&gt;Yes. Really. &lt;strong&gt;But it’s not about the graph.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s about recognizing that growth happens in more ways than we often give ourselves credit for. It’s about creating systems that remind us to value the effort we’re investing in becoming better—not just better developers, but better humans.&lt;/p&gt;

&lt;p&gt;We spend so much time optimizing our code, our workflows, our productivity. Why not optimize for the things that &lt;em&gt;actually&lt;/em&gt; matter? Health. Balance. Sustainability.&lt;/p&gt;

&lt;p&gt;GitFit is absurd in the best way. It’s a deeply technical solution to a fundamentally human problem: the need to feel like we’re making progress.&lt;/p&gt;

&lt;p&gt;If you’re someone who responds to visible metrics—and if you’re reading a programmer’s blog, you probably are—then maybe you need your own version of this. Maybe it’s syncing your reading goals, your creative projects, your volunteer hours. Whatever form of growth you’re investing in that doesn’t naturally show up in the places you look for validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build the thing that lets you see your own effort.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because when your GitHub contribution graph finally reflects the full picture of how you spent your time, you might discover something surprising: you were doing better than you thought all along.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;GitFit is open source and available on &lt;a href=&quot;https://github.com/maldins46/GitFit&quot;&gt;GitHub&lt;/a&gt;. The setup takes about 10 minutes, and it’s compatible with any Strava account. Your workout data stays private—only the commits show up.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 04 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://riccardomaldini.it/blog/gitfit/</link>
        <guid isPermaLink="true">https://riccardomaldini.it/blog/gitfit/</guid>
        
        <category>projects</category>
        
        <category>sport</category>
        
        <category>automation</category>
        
        <category>python</category>
        
      </item>
    
      <item>
        <title>My new workstation for 2025</title>
        <description>&lt;p&gt;What’s more personal than your own workstation setup?&lt;/p&gt;

&lt;p&gt;Creating the perfect setup is a thing that I genuinely enjoy doing. Choosing the right desk, chair, monitor, and accessories can make a huge difference in comfort and productivity, and I honestly love the process of researching and assembling everything, giving it a personal touch.&lt;/p&gt;

&lt;p&gt;Here in Milan I’ve moved a few times. It’s not easy to establish a perfect setup when you’re renting apartments for short periods. And most importantly, not being my own landlord means I can’t always make the changes I’d like. All of those factors brought me to not invest too much effort on that on the last three years.&lt;/p&gt;

&lt;p&gt;On my previous apartment, as an example, my set up was quite basic. And with basic, I mean a lot basic: my desk was a picnic table I’ve stolen from my parents’ house, since the previous apartment didn’t have a desk in it. And my chair, well, the classic plastic IKEA chair stolen from the living room. I’ve bought the cheaper monitor on Amazon, a cabled cheap keyboard and mouse combo, and used my old laptop as a second screen. It worked, but it wasn’t exactly ideal for long working hours.&lt;/p&gt;

&lt;p&gt;At that time, I was on my first real job after university. Having moved in Milan only a few months before, and not knowing where I’d have been the month after, I didn’t want to invest too much in a proper setup.&lt;/p&gt;

&lt;p&gt;Then two years later, I moved to my current apartment. To be honest, I was quite lucky on that one: I’m near the city center, in a nice area, with a good rent price. The apartment is not too small, and despite living still with two roommates, I have my own room, and it is a spacious one. Most importantly, I had the lucky chance to find here a desk already in place. And honestly a good one. I think it is something from IKEA, a nice white wooden one, with a good size and a clean design. It can even be heightened with some legs extension when needed, becoming a standing desk (even if it is quite tricky to do so). But the point is: I finally had a proper space to work on.&lt;/p&gt;

&lt;p&gt;On the first two years here, I kept using my old monitor and peripherals. Recently though, I decided it was time for a change. It was a combination of factors.&lt;/p&gt;

&lt;p&gt;On the first place, I started to feel uncomfortable while working from home with the old setup and peripherals. The monitor was not at the right height, the keyboard and mouse were not ergonomic at all, and the chair was just not made for long working hours.&lt;/p&gt;

&lt;p&gt;Secondly, I had a bit of luck on the working side. After a few years at the same company, I resigned and moved to another company, which provided me a dedicated budget for the home office setup. Not a huge one, but enough to make a difference. It was the perfect excuse to invest in a better setup.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-new-setup&quot;&gt;The new setup&lt;/h2&gt;

&lt;p&gt;After some research and consideration, I did few upgrades! Look at how polished and professional it looks now:&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-11-08-workstation-2025/workstation2025.jpg&quot; alt=&quot;Workstation 2025&quot; /&gt;
  &lt;figcaption&gt;My workstation in tutto il suo splendore&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Here’s a breakdown of the components:&lt;/p&gt;

&lt;h3 id=&quot;desk--chair&quot;&gt;Desk &amp;amp; Chair&lt;/h3&gt;

&lt;p&gt;My desk is a spacious, minimalist model with enough room for a monitors, a laptop, and accessories. I use an ergonomic chair that supports my back during long work sessions. An investment I highly recommend, and that in my case came for free since I’ve taken it from my previous office 👀&lt;/p&gt;

&lt;h3 id=&quot;monitors--computer&quot;&gt;Monitors &amp;amp; Computer&lt;/h3&gt;

&lt;p&gt;I run a pseudo dual-monitor setup: one 27” display for main tasks, and the MacBook Pro monitor opened as a side space, for notes, reading, and chat. There’s a small stand for the monitor to match the height of the main display. I prefer having this arrangement over like an assembled pc, for flexibility reasons: once I finish to work, I can switch to my home laptop and continue my personal project there.&lt;/p&gt;

&lt;h3 id=&quot;peripherals&quot;&gt;Peripherals&lt;/h3&gt;

&lt;p&gt;A mechanical keyboard with tactile switches and a wireless mouse make typing and navigation a pleasure. The vertical mouse is a must, to avoid any kind of wrist problem.&lt;/p&gt;

&lt;h3 id=&quot;audio--lighting&quot;&gt;Audio &amp;amp; Lighting&lt;/h3&gt;

&lt;p&gt;For calls and music, I rely on noise-cancelling headphones. Adjustable LED desk lamps provide soft, even lighting, reducing eye strain and improving video call quality.&lt;/p&gt;

&lt;h3 id=&quot;music--decor&quot;&gt;Music &amp;amp; Decor&lt;/h3&gt;

&lt;p&gt;I recently bought a new turntable, and it’s been a fantastic addition to my workspace. There’s something special about vinyl that digital music just can’t replicate, and it adds a nice aesthetic touch to the room. And to finish, a white mat over the desk ties the whole setup together.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-11-08-workstation-2025/workstationDetail2025.jpg&quot; alt=&quot;Workstation 2025&quot; /&gt;
  &lt;figcaption&gt;Turntable detail. Best self-present ever&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;p&gt;That’s it guys! That’s my setup that will accompany me over 2025, and hopefully the next few years as well. I’ve everything that I need for work and leisure, all in one place. If you have any questions about my setup or want to share yours, feel free to reach out!&lt;/p&gt;
</description>
        <pubDate>Sat, 08 Nov 2025 00:00:00 +0000</pubDate>
        <link>https://riccardomaldini.it/blog/workstation-2025/</link>
        <guid isPermaLink="true">https://riccardomaldini.it/blog/workstation-2025/</guid>
        
        <category>workstation</category>
        
        <category>productivity</category>
        
      </item>
    
      <item>
        <title>CodyColor Multiplayer: From a Thesis Project to a Startup Game</title>
        <description>&lt;p&gt;2019 was a year of big changes in my life.&lt;/p&gt;

&lt;p&gt;I had just graduated with my bachelor’s degree that February. My course was &lt;em&gt;Informatica Applicata&lt;/em&gt;: that’s roughly equivalent to Computer Science outside Italy. I already liked programming, even though, looking back from where I am now, I was just at the beginning of my journey.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-09-21-codycolor/university.jpg&quot; alt=&quot;It&apos;s me during my degree!&quot; /&gt;
  &lt;figcaption&gt;Me and my beloved colleghi urbinati&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I still remember my thesis well: &lt;em&gt;Utilizzo del pattern Publish-Subscribe e dei Message Broker nell’implementatione di giochi online Multiplayer e Applicazioni Distribuite&lt;/em&gt; 🗣📢🔥&lt;/p&gt;

&lt;p&gt;A very roboant name for a thesis, isn’t it? It was based on experiments I did with message brokers (mainly &lt;a href=&quot;https://www.rabbitmq.com/&quot;&gt;RabbitMQ&lt;/a&gt;) and the &lt;a href=&quot;https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern&quot;&gt;publish-subscribe&lt;/a&gt; pattern, during an internship at a software company in my town.&lt;/p&gt;

&lt;p&gt;It was an interesting topic, one that hadn’t been covered during my studies. But as I discovered during my internship, message brokers are essential in microservice, or even simpler multi-service architectures, especially when handling asynchronous communication.&lt;/p&gt;

&lt;p&gt;As part of my thesis, I also built a small browser-based multiplayer game. It was simple but fun: every player controlled a ninja cat who could throw fireballs at others on a Super Mario–like map.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-09-21-codycolor/ninja-cat.png&quot; alt=&quot;Example of gameplay&quot; /&gt;
  &lt;figcaption&gt;A screen from the thesis about the NinjaCat gameplay.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The real highlight, though, was the backend. Building on my RabbitMQ experiments, I developed a small Node.js application that used RabbitMQ to manage multiplayer rooms. Communication between client and server ran over WebSocket.&lt;/p&gt;

&lt;p&gt;On the day of my thesis defense, I even prepared a live demo! Without the skills to host it on a real server, I hacked together a setup on my home PC, exposing both the client and backend to the internet. It worked, and the professors could interact with the game live. Their surprised faces made it worth it.&lt;/p&gt;

&lt;p&gt;So, why am I telling you all this? Because one of those professors was particularly impressed. At that time, he was launching a university spin-off company aimed at developing small games and tools for teaching children in elementary and middle school the basics of programming. After seeing my demo, he invited me to join his company.&lt;/p&gt;

&lt;p&gt;It was an unexpected but exciting proposal. I decided to seize the opportunity and see where life would take me.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;codycolor-multiplayer&quot;&gt;CodyColor Multiplayer&lt;/h2&gt;

&lt;p&gt;About a month later, I joined the company: &lt;strong&gt;&lt;a href=&quot;https://digit.srl&quot;&gt;Digit Srl&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Digit was a small start-up, backed by the University of Urbino. Out of four employees, two were researchers continuing their projects, while the other two were recent graduates — me and another guy.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-09-21-codycolor/colleagues.jpg&quot; alt=&quot;Colleagues&quot; /&gt;
  &lt;figcaption&gt;Selfie with my Digit colleagues!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;My focus was on building a multiplayer game called &lt;a href=&quot;https://codycolor.codemooc.net&quot;&gt;&lt;strong&gt;CodyColor&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;CodyColor is actually a &lt;strong&gt;coding method&lt;/strong&gt;: a set of simple rules that can be turned into different types of games. The company already had an offline version, played with physical tiles and pawns, and the plan was to bring it online.&lt;/p&gt;

&lt;p&gt;Here are the basic rules:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The playing field is a &lt;strong&gt;5x5 grid&lt;/strong&gt; (though it can be any size).&lt;/li&gt;
  &lt;li&gt;Each square can be colored &lt;strong&gt;yellow&lt;/strong&gt;, &lt;strong&gt;red&lt;/strong&gt;, or &lt;strong&gt;grey&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Each player controls a robot, Roby, who moves based on the color of the square:
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Yellow&lt;/strong&gt; → rotate 90° left&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Red&lt;/strong&gt; → rotate 90° right&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Grey&lt;/strong&gt; → move forward&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a turn, Roby continues to the next square in its new direction.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;bringing-it-to-life&quot;&gt;Bringing It to Life&lt;/h2&gt;

&lt;p&gt;Digitalizing CodyColor was not easy, especially since I had zero experience with game development. But I did what I always do: learn by example.&lt;/p&gt;

&lt;p&gt;For the frontend, I used &lt;a href=&quot;https://angularjs.org&quot;&gt;AngularJS&lt;/a&gt; (yes, the grandparent of &lt;a href=&quot;https://angular.dev/&quot;&gt;Angular&lt;/a&gt;). For the backend, I built a NodeJS app, communicating with the client via a single WebSocket channel. All of the heavy work was made possible via &lt;a href=&quot;https://github.com/stomp-js/stompjs&quot;&gt;StompJs&lt;/a&gt;, implementing the publish-subscribe pattern over WebSocket.&lt;/p&gt;

&lt;p&gt;With some help from the colleagues, the project began to take shape. I first built a single-player version with a simple AI opponent. I worked with the HTML canvas, added drag-and-drop robots, and integrated resources from the offline version.&lt;/p&gt;

&lt;p&gt;To my surprise, the game started to come alive. Within a month, I had a working single-player prototype.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-09-21-codycolor/gameplay-cody.jpg&quot; alt=&quot;Gameplay example&quot; /&gt;
  &lt;figcaption&gt;Screens from the CodyColor game.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;switching-to-multiplayer&quot;&gt;Switching to Multiplayer&lt;/h2&gt;

&lt;p&gt;The next step, of course, was multiplayer.&lt;/p&gt;

&lt;p&gt;Over the following months, we managed to make the magic happen again. We didn’t just create a multiplayer version—we also built a &lt;strong&gt;battle royale mode&lt;/strong&gt;, where dozens (even hundreds) of players could compete in the same room.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-09-21-codycolor/children.jpeg&quot; alt=&quot;Children&quot; /&gt;
  &lt;figcaption&gt;Children from an elementary school playing with CodyColor in a Battle Royale.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We added a shared leaderboard and even allowed players to log in with federated identities. Using &lt;a href=&quot;https://firebase.google.com/docs/auth&quot;&gt;Firebase Authentication&lt;/a&gt; and &lt;a href=&quot;https://firebase.google.com/docs/firestore&quot;&gt;Firestore&lt;/a&gt;, we could manage this almost for free, given the small user base.&lt;/p&gt;

&lt;p&gt;As a fun spin-off, I developed a &lt;strong&gt;&lt;a href=&quot;https://wall.codycolor.codemooc.net&quot;&gt;wall version&lt;/a&gt;&lt;/strong&gt; of the game. It ran on a TV in our company’s display window. A QR code invited passersby to scan it, instantly joining a match against the AI visible on the screen.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-09-21-codycolor/wall.jpg&quot; alt=&quot;Wall&quot; /&gt;
  &lt;figcaption&gt;Codycolor Wall in the Digit Srl showcase.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;In the end, I had built a real product with my own hands—despite starting with almost no knowledge of JavaScript.&lt;/p&gt;

&lt;p&gt;CodyColor Multiplayer remains one of the projects I’m most proud of. Not because of its complexity, nor its code quality (honestly, the code was terrible). But because it showed me what I was capable of: learning on the go, solving problems step by step, and pushing through challenges instead of giving up.&lt;/p&gt;

&lt;p&gt;It’s one of the projects where I grew the most as a developer—and it all started with a simple opportunity I decided to take.&lt;/p&gt;
</description>
        <pubDate>Sun, 21 Sep 2025 00:00:00 +0000</pubDate>
        <link>https://riccardomaldini.it/blog/codycolor/</link>
        <guid isPermaLink="true">https://riccardomaldini.it/blog/codycolor/</guid>
        
        <category>projects</category>
        
        <category>angular</category>
        
        <category>node</category>
        
      </item>
    
      <item>
        <title>Visualizing the Pandemic: The Story Behind CovidAnalysis</title>
        <description>&lt;p&gt;The &lt;strong&gt;Covid pandemic&lt;/strong&gt; was one of the strangest periods of our lives.&lt;/p&gt;

&lt;p&gt;For a few years, the world seemed to stop in its tracks. Streets went silent, cities emptied, and our daily routines were suddenly reshaped by lockdowns and strict restrictions on movement. It was a time that forced us to rethink society, freedom, and even ourselves.&lt;/p&gt;

&lt;p&gt;And, of course, the reason behind it all was grim: &lt;strong&gt;illness and death&lt;/strong&gt;. Millions were affected by the COVID-19 virus, while millions more went through an emergency vaccination campaign that promised a way out of the crisis.&lt;/p&gt;

&lt;p&gt;In the middle of this, I was in my final year of university. The pandemic hit Italy in the early months of 2020, bringing with it the first nationwide lockdown. After a brief window of relief during spring and summer, new restrictions followed in the months ahead. Life felt like a cycle of reopening and shutting down, always with a lingering uncertainty about what would come next.&lt;/p&gt;

&lt;p&gt;But after the first shock, something unique happened in Italy—something that, as far as I know, very few countries attempted. COVID-related data—numbers of infections, hospitalizations, deaths, and later vaccinations—were made &lt;strong&gt;publicly available as open data&lt;/strong&gt;. Updated daily, they were shared on a &lt;a href=&quot;https://github.com/pcm-dpc/COVID-19&quot;&gt;dedicated GitHub repository&lt;/a&gt;. To me, this was remarkable: a real act of transparency and, in a way, efficiency from our &lt;em&gt;Protezione Civile&lt;/em&gt;. It’s not common for governments to publish this kind of information in such a structured and accessible way.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-30-covidanalysis/pcm-home.png&quot; alt=&quot;The COVID-19 Repository from the Protezione Civile&quot; /&gt;
  &lt;figcaption&gt;The COVID-19 Repository from the Protezione Civile&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It was in the autumn of 2020 that I stumbled upon this repository. By then, the air was thick with news—some accurate, some misleading, some pure speculation. That’s when a thought struck me: &lt;em&gt;what if I try to make sense of the data myself, instead of drowning in unverified headlines and rumors?&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-covidanalysis-project&quot;&gt;The CovidAnalysis Project&lt;/h2&gt;

&lt;p&gt;In November 2020, I started the &lt;strong&gt;&lt;a href=&quot;https://github.com/maldins46/CovidAnalysis&quot;&gt;CovidAnalysis Project&lt;/a&gt;&lt;/strong&gt;, a not-so-original name for a small GitHub repository where I experimented with visualizing the data published by the &lt;em&gt;Protezione Civile&lt;/em&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-30-covidanalysis/repo-home.png&quot; alt=&quot;The CovidAnalysis Project on GitHub&quot; /&gt;
  &lt;figcaption&gt;The CovidAnalysis Project on GitHub&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The repository looked very different in its early days compared to what you can see now. At first, I simply created &lt;strong&gt;basic plots using Matplotlib’s PyPlot library&lt;/strong&gt;. Nothing fancy—just simple charts to get a clearer view of the numbers.&lt;/p&gt;

&lt;p&gt;I began with the basics: the number of deaths and infections, both as weekly incidence and absolute values. I was particularly interested in the data from my own region, &lt;em&gt;Le Marche&lt;/em&gt;, since local information wasn’t always easy to find in national news.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-30-covidanalysis/chart-marche-parameters.png&quot; alt=&quot;Example of the first basic charts&quot; /&gt;
  &lt;figcaption&gt;Example of the first basic charts.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;As I got more comfortable, I started experimenting with more advanced analysis. I built geographical maps showing data per region and province, calculated derivatives to track daily increases or decreases, and even tried to estimate the infamous &lt;a href=&quot;https://tg24.sky.it/salute-e-benessere/approfondimenti/indice-rt&quot;&gt;RT and R0 indexes&lt;/a&gt; per region—later adding vaccination data as well.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-30-covidanalysis/chart-increment-provinces.jpg&quot; alt=&quot;Example of geomap chart&quot; /&gt;
  &lt;figcaption&gt;Example of geomap chart.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;What made this project truly fascinating was its &lt;strong&gt;immediacy&lt;/strong&gt;. The data wasn’t historical or archived: it was unfolding in real time. Every day I could update my charts and see the impact of new cases, comparing how infections rose or fell against the thresholds the government used to determine lockdown measures.&lt;/p&gt;

&lt;p&gt;CovidAnalysis quickly became more than just a coding exercise. For me, it was &lt;strong&gt;a powerful way to make sense of the chaotic reality around me&lt;/strong&gt;, and to replace anxiety with understanding.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;lets-go-public&quot;&gt;Let’s Go Public!&lt;/h2&gt;

&lt;p&gt;By December 2020, I started realizing that all the information I was generating was pure gold. It felt like a waste to keep it only for myself. &lt;em&gt;Wouldn’t it be great to share these insights with others?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The first step was &lt;strong&gt;automation&lt;/strong&gt;. I set up a GitHub Action with a cron job. Every morning at 4 a.m., the Action would spin up a container, run the Python scripts, generate fresh plots, and export them as JPEGs. The images were then committed back into the repository on the deploy branch.&lt;/p&gt;

&lt;p&gt;The second step was &lt;strong&gt;exposing the data&lt;/strong&gt;. I decided to build a small Angular Progressive Web App (PWA) to display all the charts my scripts generated daily.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-30-covidanalysis/covidanalysis-home.png&quot; alt=&quot;PC screenshot&quot; /&gt;
  &lt;figcaption&gt;How CovidAnalysis appears from PC&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In less than a week, I had &lt;a href=&quot;https://maldins46.github.io/CovidAnalysis&quot;&gt;the first version up and running&lt;/a&gt;. To keep things simple (and free), I hosted it directly on GitHub Pages. Zero cost, minimal friction, and suddenly my data wasn’t just mine anymore—it was available to anyone.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;tweaks-and-optimizations&quot;&gt;Tweaks and Optimizations&lt;/h2&gt;

&lt;p&gt;Over time, the website became more than just a data portal—it turned into a playground for experimenting with style and features. I started treating it as a design exercise, trying out &lt;strong&gt;Angular’s Material UI&lt;/strong&gt; and fine-tuning the details of the interface.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-30-covidanalysis/phone-screens.png&quot; alt=&quot;Phone screenshots&quot; /&gt;
  &lt;figcaption&gt;How CovidAnalysis appears from mobile&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I also restructured the site into &lt;strong&gt;multiple sections&lt;/strong&gt;: one dedicated to national Italian data, another focused on my home region of &lt;em&gt;Le Marche&lt;/em&gt;, and a third where I pulled in pandemic-related news directly from Twitter.&lt;/p&gt;

&lt;p&gt;On the design side, I couldn’t resist adding a &lt;strong&gt;dark mode&lt;/strong&gt;, along with an automatic theme switch based on the user’s system preferences.&lt;/p&gt;

&lt;p&gt;But the feature I’m most proud of was the &lt;strong&gt;notification system&lt;/strong&gt;. I experimented with push notifications: whenever new daily data was processed, users would receive a notification. To achieve this, I combined service workers with a minimal backend deployed on Heroku.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-30-covidanalysis/covidanalysis-architecture.png&quot; alt=&quot;CovidAnalysis high level architecture&quot; /&gt;
  &lt;figcaption&gt;CovidAnalysis high level architecture&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;What started as a small experiment in my final year of university quickly grew into something much bigger. CovidAnalysis was never meant to be a polished product. It was a playground where I combined curiosity, coding, and a very real need to make sense of the world around me.&lt;/p&gt;

&lt;p&gt;Along the way, I learned how to fetch and process open data, automate workflows with GitHub Actions, build and deploy a frontend with Angular, and even push the boundaries of PWAs with notifications. But more than the technical lessons, this project gave me something deeper: a way to cut through the noise of misinformation and uncertainty during one of the most confusing times of our lives.&lt;/p&gt;

&lt;p&gt;Looking back, I see CovidAnalysis as more than just a coding project. It was &lt;strong&gt;my way of understanding reality through data&lt;/strong&gt;, of feeling a little more in control when everything around us felt unpredictable.&lt;/p&gt;
</description>
        <pubDate>Sat, 30 Aug 2025 00:00:00 +0000</pubDate>
        <link>https://riccardomaldini.it/blog/covidanalysis/</link>
        <guid isPermaLink="true">https://riccardomaldini.it/blog/covidanalysis/</guid>
        
        <category>projects</category>
        
        <category>angular</category>
        
        <category>python</category>
        
      </item>
    
      <item>
        <title>The Comepaolo Blog: How I built my personal website</title>
        <description>&lt;p&gt;How has this website been built?&lt;/p&gt;

&lt;p&gt;If you work in software and you’re wandering around my blog, maybe this is the first question that comes to mind.
Is the blog powered by a CMS? Is it just a static website, an Angular PWA? Could I build something similar for myself?&lt;/p&gt;

&lt;p&gt;If you’ve ever thought about that, you’re in the right place. Let’s start from the beginning.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-28-my-website/cover-image.png&quot; alt=&quot;Meme post&quot; /&gt;
  &lt;figcaption&gt;Please appreciate my meme talent, thank you&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-origins&quot;&gt;The origins&lt;/h2&gt;

&lt;p&gt;When I first bought the domain &lt;strong&gt;riccardomaldini.it&lt;/strong&gt;, turning it into a blog wasn’t part of the plan.&lt;/p&gt;

&lt;p&gt;We’re talking about 2018. I was still in university, just starting to dive into the magical world of software development. During a Networking course, I began to understand how the Internet actually worked. This whole network of websites linked together across the world seemed almost magical.&lt;/p&gt;

&lt;p&gt;That’s also when I started thinking about my digital identity: what if I bought a domain for myself? The domain riccardomaldini.it was still unused, and it would’ve been a shame if someone else took it and used it for something unrelated to me.&lt;/p&gt;

&lt;p&gt;So I decided to buy it. I had no idea what to do with it at the time. I couldn’t even build a website yet, but I wanted to secure it for the future me. On Aruba (the hosting provider I used), the price was ridiculously cheap — something like 3 euros per year — so I didn’t hesitate.&lt;/p&gt;

&lt;p&gt;The first real use of the domain came from my Play Store projects. Publishing apps on the Play Store requires providing Google with a webpage containing a Privacy Policy and Terms &amp;amp; Conditions. So I purchased hosting space from Aruba and uploaded a very simple static site with those policies (auto-generated, of course — I’m not a lawyer yet 🙂).&lt;/p&gt;

&lt;h3 id=&quot;the-first-version-a-very-static-cv-website&quot;&gt;The first version: a very static CV website&lt;/h3&gt;

&lt;p&gt;Fast forward to 2021. We’re in the middle of the COVID pandemic. I was in my last year of university with a few exams left, but also with much more experience than before and, for the first time, &lt;em&gt;a lot&lt;/em&gt; of free time (thanks, lockdown).&lt;/p&gt;

&lt;p&gt;That’s when I started experimenting heavily with &lt;strong&gt;web design&lt;/strong&gt;. I was learning Flutter, Angular, and even some Python/TypeScript for my first web server experiments. I worked on the DiAry project and &lt;a href=&quot;https://maldins46.github.io/CovidAnalysis/home&quot;&gt;CovidAnalysis&lt;/a&gt; (I’ll definitely talk about those in the future). At the same time, I began experimenting again with my personal website.&lt;/p&gt;

&lt;p&gt;My first idea was to &lt;strong&gt;turn it into an online CV&lt;/strong&gt;. I wanted a place to showcase my studies, skills, and (limited) work experience. So I developed the first version: a &lt;strong&gt;static, hand-written HTML page with CSS&lt;/strong&gt;, built on top of a template from &lt;a href=&quot;https://html5up.com&quot;&gt;html5up.com&lt;/a&gt;. Honestly, I think everyone started from there 😄&lt;/p&gt;

&lt;p&gt;The first template I used was &lt;a href=&quot;https://html5up.net/photon&quot;&gt;Photon&lt;/a&gt;, a versatile personal template. Then I tried &lt;a href=&quot;https://html5up.net/miniport&quot;&gt;Miniport&lt;/a&gt;, which was a bit more CV-oriented, and finally settled on &lt;a href=&quot;https://html5up.net/strata&quot;&gt;Strata&lt;/a&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-28-my-website/strata-theme.png&quot; alt=&quot;The Strata Website&quot; /&gt;
  &lt;figcaption&gt;The first version of the website, created with Strata.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It worked for its purpose… but it was also very limited. It was just a static page, and every change required a lot of manual editing in HTML and CSS.&lt;/p&gt;

&lt;h3 id=&quot;the-first-paradigm-shift-file-based-cms&quot;&gt;The first paradigm shift: file-based CMS&lt;/h3&gt;

&lt;p&gt;Another option was to use a CMS. I experimented with WordPress, and I liked the idea of having a “framework” to handle all the logic behind the scenes so I could focus on writing content. But I quickly ran into three problems:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;WordPress is &lt;strong&gt;primarily designed for blogs&lt;/strong&gt;. Plugins make it very flexible, but using it for a CV website always felt a bit forced.&lt;/li&gt;
  &lt;li&gt;The &lt;strong&gt;CV templates&lt;/strong&gt; available were either ugly, paid, or really hard to customize.&lt;/li&gt;
  &lt;li&gt;WordPress &lt;strong&gt;requires a MySQL database and a PHP runtime&lt;/strong&gt;. Each of these adds cost to a hosting plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this made me look for alternatives.&lt;/p&gt;

&lt;p&gt;The breakthrough came when I discovered the concept of &lt;strong&gt;file-based CMS&lt;/strong&gt;. This idea, which started gaining momentum around 2020, offered an alternative: instead of using a database, the CMS would rely on the filesystem itself, saving content as regular files. This immediately caught my interest — especially because it meant I could avoid paying for a MySQL database.&lt;/p&gt;

&lt;p&gt;That’s how I found &lt;strong&gt;&lt;a href=&quot;https://getgrav.org&quot;&gt;Grav&lt;/a&gt;&lt;/strong&gt;, a simple file-based CMS that worked perfectly. It even provided “skeletons” — pre-made templates you could unpack directly into your hosting space and start using right away.&lt;/p&gt;

&lt;p&gt;I switched to Grav with a CV-themed skeleton called &lt;a href=&quot;https://github.com/devlom/grav-skeleton-hola&quot;&gt;Hola&lt;/a&gt;, which I heavily customized for my needs. This version of the website lasted for years. It was flexible, easy to use, and very low-maintenance. Adding a new project, education, or work experience was as simple as editing a post and uploading a few photos — Grav handled the rest.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-28-my-website/hola-theme.jpg&quot; alt=&quot;The Hola Website&quot; /&gt;
  &lt;figcaption&gt;A snapshot from the original Grav Hola Template. For a long time, my website was a copycat of this one.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;shifting-to-the-blog-approach&quot;&gt;Shifting to the blog approach&lt;/h3&gt;

&lt;p&gt;At the beginning of 2025, I started rethinking the purpose of my website. My life had changed. Presenting myself only through a CV felt reductive. I’m not just a list of education and work experience.&lt;/p&gt;

&lt;p&gt;Sure, you can download my CV if you want, but a CV doesn’t give me space to elaborate — to write full articles about projects or experiences, like the one you’re reading right now.&lt;/p&gt;

&lt;p&gt;That’s when I decided to switch to a blog format. I created &lt;strong&gt;The Comepaolo Blog&lt;/strong&gt; with a new purpose: to have a place where I could share and discuss my projects and experiences in depth.&lt;/p&gt;

&lt;p&gt;The first version of the blog used, once again, a Grav theme: &lt;a href=&quot;https://github.com/getgrav/grav-skeleton-mediator-site&quot;&gt;Mediator&lt;/a&gt;, a theme inspired by Medium’s design. It’s actually the same theme I still use today, even if a lot has changed under the hood.&lt;/p&gt;

&lt;p&gt;I created the initial content, set up the skeleton of the site, and added a few customizations. I even &lt;a href=&quot;https://github.com/getgrav/grav-theme-mediator/pull/25&quot;&gt;contributed to the open source project of the theme&lt;/a&gt; with some optimizations.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-28-my-website/mediator-blog.png&quot; alt=&quot;The Mediator-themed Website&quot; /&gt;
  &lt;figcaption&gt;The actual bloggish-style of the website, based on the Mediator theme&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;the-second-paradigm-shift-jekyll&quot;&gt;The second paradigm shift: Jekyll&lt;/h3&gt;

&lt;p&gt;More recently, I started experimenting with Jekyll. This came mainly from professional needs — I had to build a documentation website for an API set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jekyll&lt;/strong&gt; is a static website generator. It lets you write your content as Markdown files (or other markup formats), completely separating content from presentation. It’s written in Ruby and uses a meta-language called Liquid to customize templates.&lt;/p&gt;

&lt;p&gt;I fell in love with Jekyll almost instantly. It’s easy to use, the learning curve isn’t steep, and the concept behind it is clever and straightforward. GitHub even supports it out of the box for GitHub Pages, making it the de-facto standard for static websites.&lt;/p&gt;

&lt;p&gt;While digging around, I discovered that the Mediator theme for Grav was actually a port of a Jekyll template — also called &lt;a href=&quot;https://github.com/dirkfabisch/mediator&quot;&gt;Mediator&lt;/a&gt;. At that point, the path was obvious: it was time to migrate my website to Jekyll and turn it into a real software project.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-comepaolo-blog-project&quot;&gt;The comepaolo-blog Project&lt;/h2&gt;

&lt;p&gt;The current version of my website lives inside the &lt;strong&gt;&lt;a href=&quot;https://github.com/maldins46/comepaolo-blog&quot;&gt;comepaolo-blog project&lt;/a&gt;&lt;/strong&gt;, which started as a fork of the &lt;a href=&quot;https://github.com/dirkfabisch/mediator&quot;&gt;Mediator Jekyll theme&lt;/a&gt; originally created by &lt;a href=&quot;https://github.com/dirkfabisch/&quot;&gt;dirkfabisch&lt;/a&gt;. Mediator was designed as a clean, Medium-inspired theme, and it gave me a solid and elegant foundation to build upon.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-28-my-website/comepaolo-blog-github.png&quot; alt=&quot;The Comepaolo Blog Project&quot; /&gt;
  &lt;figcaption&gt;The actual comepaolo-blog project on GitHub.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I didn’t just fork it and leave it untouched, though. Over time, I shaped it to better reflect my needs and style. Some changes were simple customizations to the &lt;strong&gt;blog layout and sections&lt;/strong&gt;, while others were more structural. For example, I extended the &lt;strong&gt;post tag system&lt;/strong&gt; so that tags are now more prominent: each post shows its tags, and clicking on them brings you to a dedicated page listing all related articles.&lt;/p&gt;

&lt;p&gt;Another important addition was the &lt;strong&gt;legal section&lt;/strong&gt;. On my Grav site, I had hosted privacy policies and terms for my apps, and I wanted a smooth transition without breaking links. I rebuilt that section in Jekyll with a design consistent with the rest of the site.&lt;/p&gt;

&lt;p&gt;I also introduced the possibility to &lt;strong&gt;react and discuss about a given post&lt;/strong&gt; using &lt;a href=&quot;https://giscus.app&quot;&gt;Giscus&lt;/a&gt;, an open source alternative to Disqus, based on the GitHub API and the Discussions feature of the repository.&lt;/p&gt;

&lt;p&gt;The biggest structural shift, however, was in how the site is deployed. Instead of manually uploading files or relying on a provider’s hosting tools, the whole process now runs through &lt;strong&gt;GitHub Actions&lt;/strong&gt;. Each commit to the main branch triggers an automated build and deploy, and the output is directly published on GitHub Pages under my personal domain.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;From static HTML pages, to Grav, and now to Jekyll, the website has grown step by step along with my own path as a developer. What began as a simple CV placeholder has become a fully versioned, automated, and customizable blog.&lt;/p&gt;

&lt;p&gt;The main advantage of this setup is that it behaves like a proper software project. Every article, design tweak, or configuration change is tracked in Git; deployment happens automatically through GitHub Actions; and the structure encourages experimentation without the fear of breaking things.&lt;/p&gt;

&lt;p&gt;In the end, Comepaolo Blog is still just my personal space online—but one that reflects not only who I am, but also how I work.&lt;/p&gt;

&lt;p&gt;And for me, &lt;strong&gt;that combination feels just right&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Article update (March 2026): I’ve rewritten the blog template (again!) with my friend Claude Code, updating the theme to its v2! An article is incoming, see the new code under the &lt;a href=&quot;https://github.com/maldins46/comepaolo-blog-v2&quot;&gt;Comepaolo v2&lt;/a&gt; repo!&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 28 Aug 2025 00:00:00 +0000</pubDate>
        <link>https://riccardomaldini.it/blog/my-website/</link>
        <guid isPermaLink="true">https://riccardomaldini.it/blog/my-website/</guid>
        
        <category>projects</category>
        
        <category>jekyll</category>
        
        <category>ruby</category>
        
      </item>
    
      <item>
        <title>MaldiniCV: How I automated my Curriculum Vitae with GitHub Actions</title>
        <description>&lt;p&gt;Every student coming from a science-related university has, at some point, experimented with writing documents in &lt;a href=&quot;https://www.latex-project.org&quot;&gt;LaTeX&lt;/a&gt;. It’s a neat system designed for preparing documents, mostly used for academic purposes (nearly every scientific paper in the world is written in LaTeX), but not limited to that. Unlike tools such as Word, LaTeX is a &lt;em&gt;document preparation system&lt;/em&gt;. It separates the content of the document from its presentation (the template), and makes it easy to represent complex elements like mathematical formulas in a textual way.&lt;/p&gt;

&lt;p&gt;LaTeX templates are often professional and fine-tuned. The system itself feels almost like programming — technically, it’s a markup language — which is one of the reasons it became so popular in the scientific world.&lt;/p&gt;

&lt;p&gt;I first discovered LaTeX at university, and I kept using it well beyond that context. One of the first “non-academic” uses I found was writing my Curriculum Vitae. At some point, I stumbled upon a template called &lt;a href=&quot;https://www.overleaf.com/latex/templates/luxsleek-cv/qbvbqmrzxwyj&quot;&gt;LuxSleek-CV&lt;/a&gt;, originally created at the University of Luxembourg. It struck the perfect balance between elegance and readability, and it quickly became my official CV template.&lt;/p&gt;

&lt;p&gt;But writing it — whether in Overleaf or locally — soon revealed a bigger problem: &lt;strong&gt;versioning&lt;/strong&gt;. Maintaining my CV wasn’t as simple as I’d hoped. Every update meant opening my editor, re-compiling the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.tex&lt;/code&gt; file, exporting the PDF, and making sure I didn’t overwrite the wrong version. Over time, I ended up with a folder full of files named things like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CV-final-v2.pdf&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CV-updated.pdf&lt;/code&gt;, and of course, the infamous &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CV-really-final.pdf&lt;/code&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-23-maldini-cv/versioning-hell.png&quot; alt=&quot;The versioning hell&quot; /&gt;
  &lt;figcaption&gt;The versioning hell without a versioning system. This could escalate very quickly, trust me.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The struggle was real. At one point, I even gave up and switched to a simple Word document — probably the least “programmer” thing a programmer could do.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;maldinicv&quot;&gt;MaldiniCV&lt;/h2&gt;

&lt;p&gt;I spend my days working with version control, automation, and CI/CD pipelines. So I asked myself: why should my CV be exempt from those good practices?&lt;/p&gt;

&lt;p&gt;That’s how &lt;a href=&quot;https://github.com/maldins46/MaldiniCV&quot;&gt;MaldiniCV&lt;/a&gt; was born.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-23-maldini-cv/maldini-cv-github.png&quot; alt=&quot;Github Project&quot; /&gt;
  &lt;figcaption&gt;MaldiniCV project on GitHub.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Instead of treating my resume like a static document, I decided to treat it like a piece of software. The content lives in a GitHub repository, written in LaTeX, and every time I want to “release” a new version of my CV, I simply push a Git tag. Behind the scenes, a GitHub Actions workflow compiles the source, generates a polished PDF, and publishes it as a release artifact. No manual steps, no confusion about which file is the latest — just clean automation.&lt;/p&gt;

&lt;p&gt;From there, I structured the repository much like a small application. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;src/&lt;/code&gt; folder holds my main LaTeX file, while the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows/&lt;/code&gt; directory contains the automation logic. The heart of it all is a workflow that installs the right TeX packages, builds the PDF, and attaches it to a GitHub release.&lt;/p&gt;

&lt;p&gt;It might seem like overkill for a CV, but in practice it means I never have to think about formatting quirks or local environment issues again. If I can push code, I can update my resume.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-workflow-in-action&quot;&gt;The Workflow in Action&lt;/h2&gt;

&lt;p&gt;Here’s how it plays out in real life.&lt;/p&gt;

&lt;p&gt;Let’s say I add a new job or a side project to my CV. I commit the change to the LaTeX source and push it up to GitHub. When I’m ready to “publish” the new edition, I tag it like I would a software release:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git tag v1.2.0
git push origin v1.2.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That tag is the trigger. GitHub Actions wakes up, installs a LaTeX environment, compiles my CV into a PDF, and creates a brand-new release in the repository. Within a minute or two, the final document is available to download straight from the Releases page.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-08-23-maldini-cv/maldini-cv-releases.png&quot; alt=&quot;Releases page&quot; /&gt;
  &lt;figcaption&gt;Releases screen of the project, with the version v1.0.0&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;What I love about this is how natural it feels. Versioning my CV is just like versioning code: the history is visible, the process is repeatable, and the output is always reliable.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;why-automate-a-cv&quot;&gt;Why Automate a CV?&lt;/h2&gt;

&lt;p&gt;On the surface, automating a CV might sound like a niche experiment. But I’ve found it surprisingly impactful.&lt;/p&gt;

&lt;p&gt;For one thing, it eliminates all the little frictions of keeping documents current. I don’t worry about which PDF I last sent, or whether the formatting broke on my machine after a package update. The build process is consistent and runs in the cloud.&lt;/p&gt;

&lt;p&gt;It also adds a layer of professionalism. Each release of my CV is versioned, timestamped, and archived. If someone asked me for the version I used in an application months ago, I could retrieve it instantly. It’s like having a changelog of my professional life.&lt;/p&gt;

&lt;p&gt;And maybe the biggest win: peace of mind. I know that the link to my GitHub Releases page always points to an authoritative, up-to-date version of my resume. That alone makes it worth it. When needed, it is sufficient to provide to a recruiter a link to the &lt;a href=&quot;https://github.com/maldins46/MaldiniCV/releases/latest/download/cv-maldini.pdf&quot;&gt;latest release&lt;/a&gt;, as for any software library. Cool, isn’t it? :)&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Right now, MaldiniCV does one thing well: it builds and releases my CV automatically. It is an open source project, you can even fork it and make it yours.&lt;/p&gt;

&lt;p&gt;In the end, though, the beauty of this setup is its simplicity. My CV has become just another project — one that benefits from the same tools and practices I use every day as a software engineer. And that feels exactly right.&lt;/p&gt;
</description>
        <pubDate>Sat, 23 Aug 2025 00:00:00 +0000</pubDate>
        <link>https://riccardomaldini.it/blog/maldini-cv/</link>
        <guid isPermaLink="true">https://riccardomaldini.it/blog/maldini-cv/</guid>
        
        <category>projects</category>
        
        <category>automation</category>
        
        <category>latex</category>
        
        <category>cv</category>
        
      </item>
    
      <item>
        <title>Hoops in the Heart of Jesi: The Rise of Canestreet 3x3</title>
        <description>&lt;p&gt;&lt;strong&gt;Summer, 2018 — Jesi.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It was one of those summers when time feels endless. I had just wrapped up my internship and was halfway through writing my bachelor’s thesis. Honestly, the hardest part of my university career was already behind me.&lt;/p&gt;

&lt;p&gt;The days were long and slow. And then—someone had an idea.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-spark&quot;&gt;The Spark&lt;/h2&gt;

&lt;p&gt;Basketball is kind of a big deal around here. Jesi spent several years in Serie A before facing some unfortunate setbacks. The passion for the sport stuck around, and many locals still play.&lt;/p&gt;

&lt;p&gt;One of those hot summer evenings, I met up with my group of friends in the city center. Among those friends was Michele, 21 at the time. He’d always played basketball, and was one of the first in our group to land a steady job. But his real superpower? He’s the guy who &lt;em&gt;organizes&lt;/em&gt;. Planning Ferragosto? No problem—Michi’s already booked a camping spot in San Vicino. Want to go on holiday in Greece? He’s found the cheapest hotel in the archipelago and made the reservation. He’s the rock of the group.&lt;/p&gt;

&lt;p&gt;So when he suggested &lt;em&gt;turning the San Sebastiano parish into a basketball arena,&lt;/em&gt; we knew he meant business.&lt;/p&gt;

&lt;p&gt;And just like that, &lt;strong&gt;The Canestreet&lt;/strong&gt; was born.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-first-time&quot;&gt;The First Time&lt;/h2&gt;

&lt;p&gt;Jesi wasn’t new to basketball tournaments. Years ago, some were held at the sports complex near the high school, and even earlier, right in the main square—with big-name guests. But it had been a while since anything like that had happened. The goal was simple: fill the summer void with something fun. 3x3 basketball is a street sport—games take place on a half-court, tournament-style, with short, fast-paced matches.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-22-canestreet/canestreet2018jumping.jpg&quot; alt=&quot;Ballers under SanSeba&apos;s sunset&quot; /&gt;
  &lt;figcaption&gt;Ballers under SanSeba&apos;s sunset&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The plan was to host an amateur tournament with some of Michele’s teammates. The venue? San Sebastiano, a parish in downtown Jesi, just across from his house. The tournament’s name: &lt;strong&gt;The Canestreet&lt;/strong&gt;. Why that name? Honestly, no clue. It’s a mashup of &lt;em&gt;canestro&lt;/em&gt; (basket) and &lt;em&gt;street&lt;/em&gt;, not even quite captivating, actually. But when Michi makes a call, there’s no room for democracy.&lt;/p&gt;

&lt;p&gt;To pull it off, Michele needed a team. A few of our friends jumped in immediately—Federico, his best friend, and Lorenzo, a teammate. Soon after, I joined too.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-22-canestreet/canestreet2018chill.jpg&quot; alt=&quot;The first Canestreet chillin&apos;&quot; /&gt;
  &lt;figcaption&gt;That&apos;s me! And the staff too, chillin&apos; under the tendone&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I had never played basketball. But I had something else: a laptop and a bit of creativity. So I became the computer guy—running our Instagram page, trying to hype up the event with stories and posts, and experimenting with ways to reach people. I also ended up being the sound guy—plugging in the speakers, cueing up the playlists, packing everything down at night. It wasn’t glamorous, but it was fun. And all of that, with exactly zero euros in the budget.&lt;/p&gt;

&lt;p&gt;Within a month, the Michele Mosca organizational machine was in full swing. We gathered enough teams, secured permission from the parish, bought a few cheap medals, and pulled off our first tournament. We had no sponsors (well, none we could publicly acknowledge), and no funding.&lt;/p&gt;

&lt;p&gt;But it worked. The event was a blast.&lt;/p&gt;

&lt;p&gt;And most importantly—it &lt;em&gt;felt&lt;/em&gt; like something.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-22-canestreet/firstCanestaff.jpg&quot; alt=&quot;The First Canestaff&quot; /&gt;
  &lt;figcaption&gt;The first canestaff, est. 2018&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;growing-pains-and-giant-leaps&quot;&gt;Growing Pains and Giant Leaps&lt;/h2&gt;

&lt;p&gt;That first success was a spark, and it lit a fire. The following year, we moved to a larger space: &lt;strong&gt;San Pietro Martire&lt;/strong&gt;, a parish with more room and more visibility. We joined the official &lt;strong&gt;FIP 3x3 Summer Circuit&lt;/strong&gt;. We added categories. Got real sponsors. Learned to plan better. Messed up, learned again.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-22-canestreet/2020finals.jpg&quot; alt=&quot;2020 Finals&quot; /&gt;
  &lt;figcaption&gt;Snapshot from the 2020 finals&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Every summer became a new challenge.&lt;/p&gt;

&lt;p&gt;By 2024, we had moved to &lt;strong&gt;Piazza della Repubblica&lt;/strong&gt;—the beating heart of Jesi. With support from the municipality, we installed a modular court and hoop in the middle of the square, right under the obelisk. The sound of basketballs bouncing off cobblestones became the new summer anthem.&lt;/p&gt;

&lt;p&gt;We introduced under-age categories, both male and female open tournaments, and even a 3-point contest, including a version just for amateurs. Each edition brought new energy, new faces, and new memories.&lt;/p&gt;

&lt;p&gt;No exaggeration: Canestreet has become a true part of Jesi’s summer identity—and equally a part of &lt;em&gt;our&lt;/em&gt; lives as organizers. It’s all thanks to Michele’s wild idea and the drive of a guy who keeps leading us on this crazy ride.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-22-canestreet/repubblica2025.jpg&quot; alt=&quot;Piazza della Repubblica 2025&quot; /&gt;
  &lt;figcaption&gt;The Canestreet in Piazza Della Repubblica for the second time, 2025&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;side-projects-big-lessons&quot;&gt;Side Projects, Big Lessons&lt;/h2&gt;

&lt;p&gt;Along the way, &lt;strong&gt;Canestreet&lt;/strong&gt; became more than a tournament. It became my personal playground for experimentation.&lt;/p&gt;

&lt;h3 id=&quot;the-canescoreboard&quot;&gt;The CaneScoreboard&lt;/h3&gt;

&lt;p&gt;In the early years, we didn’t have access to electronic scoreboards. So I built one.
&lt;strong&gt;The CaneScoreboard&lt;/strong&gt; (&lt;a href=&quot;https://github.com/maldins46/ThecaneScoreboard&quot;&gt;github&lt;/a&gt;, &lt;a href=&quot;https://thecanescoreboard.web.app/login&quot;&gt;live demo&lt;/a&gt;) is a simple web app connected to a Firebase backend that lets us display live scores, fouls, and time. One screen is in editor mode (for updates), the other in display mode (for the audience). Low-latency. Real-time. It may not be beautiful, but it works—and it feels amazing to see people rely on something you made with your own hands.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-22-canestreet/scoreboard.jpg&quot; alt=&quot;TheCaneScoreboard display&quot; /&gt;
  &lt;figcaption&gt;TheCaneScoraboard display mode in place, 2019&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;the-website--thecanestreetit&quot;&gt;The Website — thecanestreet.it&lt;/h3&gt;

&lt;p&gt;I also created and maintained &lt;a href=&quot;https://thecanestreet.it/&quot;&gt;the website&lt;/a&gt;, which serves as our digital headquarters.
Built with WordPress, it includes sign-up forms, live brackets and results, sponsor visibility, and tournament rules. It may not be a full-blown platform, but it’s a living, evolving part of the project—and a place where I got to mix my coding, UX, and design skills.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-22-canestreet/websiteHomepage2026.png&quot; alt=&quot;Website Homepage&quot; /&gt;
  &lt;figcaption&gt;thecanestreet.it homepage&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&quot;the-instagram--canestreet3x3&quot;&gt;The Instagram — @canestreet3x3&lt;/h3&gt;

&lt;p&gt;Our &lt;a href=&quot;https://www.instagram.com/canestreet3x3&quot;&gt;Instagram page&lt;/a&gt; is where the vibes live.
I collaborate with friends who shoot photos, and I design infographics and announcements using tools like Canva and GIMP. Social media may not win tournaments, but it definitely brings people to them.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;Canestreet&lt;/strong&gt; started as a small neighborhood project. Today, it’s a city-wide celebration of basketball, friendship, and initiative. And for me, it’s been a personal lab—an experience that mixed creativity, technology, and community spirit in the best possible way. I can’t wait to see where it takes us next!&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-22-canestreet/crew.jpg&quot; alt=&quot;&quot; /&gt;
&lt;/figure&gt;
</description>
        <pubDate>Sun, 22 Jun 2025 00:00:00 +0000</pubDate>
        <link>https://riccardomaldini.it/blog/canestreet-3x3/</link>
        <guid isPermaLink="true">https://riccardomaldini.it/blog/canestreet-3x3/</guid>
        
        <category>basketball</category>
        
        <category>sport</category>
        
      </item>
    
      <item>
        <title>Live Scores, Real Pride: The Story Behind BetAssist</title>
        <description>&lt;p&gt;&lt;strong&gt;“What’s the project you’re most proud of?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every software developer has faced this question at least once. Maybe during an interview, maybe in a conversation with a colleague, a friend—or even with themselves. Most of the time, it’s not an easy one to answer.&lt;/p&gt;

&lt;p&gt;It was June 2022. I was deep into the hiring process with a foreign software company. Not a big tech giant, but one of those solid mid-sized companies with a structured, multi-step approach. One of those steps was a behavioral interview. The interviewer asked about my values, how I approached challenges, and how I’d handled difficult situations.&lt;/p&gt;

&lt;p&gt;That’s when the famous question came up again.&lt;/p&gt;

&lt;p&gt;I paused for a few seconds. I had worked on many projects over the years, so picking just one wasn’t easy. But after a moment of reflection, I decided to stop overthinking and go with the most honest answer that came to mind:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“BetAssist.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That was my bet.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-16-betassist/header.png&quot; alt=&quot;First BetAssist header on the Google Play store.&quot; /&gt;
  &lt;figcaption&gt;First BetAssist header on the Google Play store.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-idea&quot;&gt;The Idea&lt;/h2&gt;

&lt;p&gt;I genuinely think BetAssist is the project I’m most proud of. It’s an Android app I started building back in 2017. Its main purpose? To &lt;strong&gt;track football betslips&lt;/strong&gt; with live score updates and offer betting suggestions.&lt;/p&gt;

&lt;p&gt;For the unfamiliar, a &lt;strong&gt;betslip&lt;/strong&gt; is essentially a form that records the details of a sports bet. At the time, in Italy, it was still common to place bets in physical shops and walk away with a paper receipt. You had to track your results manually—either by following each game individually or checking scores across various apps or websites. The official betting apps either weren’t great yet or weren’t widely adopted.&lt;/p&gt;

&lt;p&gt;That’s when the idea hit me:
&lt;em&gt;What if all of this could be done in a single app?&lt;/em&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-process&quot;&gt;The Process&lt;/h2&gt;

&lt;p&gt;In 2017, I was in my second year of a Computer Science degree. I had just finished an object-oriented programming course in Java and was eager to apply those concepts in the real world.&lt;/p&gt;

&lt;p&gt;At that time, Android apps were still written in Java. So I picked up a book I still have in my Google Drive to this day: &lt;a href=&quot;https://github.com/PacktPublishing/Android-Programming-for-Beginners-Third-Edition&quot;&gt;&lt;em&gt;Android Programming for Beginners&lt;/em&gt; by John Horton&lt;/a&gt;. It was a hands-on introduction to Android development using Android Studio. The book’s main project was a simple note-taking app, which was used to teach UI design, app lifecycle, persistence, networking, and more.&lt;/p&gt;

&lt;p&gt;I followed the guide chapter by chapter. But in the back of my mind, I was already thinking about BetAssist.
&lt;em&gt;“This ListView for notes would be perfect for a betslip list!”&lt;/em&gt;
That single thought drove my motivation and learning throughout the book.&lt;/p&gt;

&lt;p&gt;By the time I finished, I was ready to move on to something of my own.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-mvp&quot;&gt;The MVP&lt;/h2&gt;

&lt;p&gt;The first version of BetAssist was simple: a &lt;strong&gt;betslip editor&lt;/strong&gt;.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-16-betassist/mascotte.jpg&quot; alt=&quot;The BetAssist Mascotte&quot; /&gt;
  &lt;figcaption&gt;BetAssist had a mascotte also. It didn&apos;t have a name, but was used on some of the graphics and logo, inspired by the Android original logo.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Users could create betslips by combining clubs from various European leagues. They could define matches, choose outcomes like 1X2 or over/under 2.5 goals, assign dates, and—crucially—receive phone notifications when the matches ended.&lt;/p&gt;

&lt;p&gt;I had so much fun building it. I loved the immediacy of Android Studio. I could see results instantly, even on my personal phone. I even designed the UI myself—experimenting with colors, layouts, and a logo (which got me to learn a bit of Photoshop along the way).&lt;/p&gt;

&lt;p&gt;But despite how fun it was, I knew it wasn’t quite &lt;em&gt;there&lt;/em&gt; yet.&lt;/p&gt;

&lt;p&gt;It was, at that stage, little more than a glorified note-taking app. It hadn’t yet solved the core problem. It wasn’t a product—it was a personal sandbox. But I already had ideas for version 2.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;things-getting-live&quot;&gt;Things Getting Live&lt;/h2&gt;

&lt;p&gt;To become truly useful, BetAssist needed one critical upgrade: &lt;strong&gt;live scores&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I didn’t want to get tangled in web scraping, and there was no chance I’d enter scores manually. So I started looking for APIs. That’s when I found &lt;a href=&quot;https://www.football-data.org&quot;&gt;Football-Data.org&lt;/a&gt;—a free API with access to near real-time football scores from major European leagues.&lt;/p&gt;

&lt;p&gt;The free tier was generous and perfect for my needs. Even today, I think it’s a gem: live scores (with a ~10-minute delay), solid coverage, and reasonable rate limits. For a small monthly fee, you could even unlock global data—but I never needed to.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-16-betassist/architecture.png&quot; alt=&quot;Architecture Overview&quot; /&gt;
  &lt;figcaption&gt;Quick overview on the BetAssist Architecture&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Like many early developers, I started small. A university friend and I built a backend in &lt;strong&gt;PHP&lt;/strong&gt; (yes, forgive us—it was all we knew besides Java), hosted on &lt;strong&gt;Heroku&lt;/strong&gt;. Every 10 minutes, the backend would ping the Football-Data API, collect score updates, and store them in &lt;strong&gt;Google Firestore&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why Firestore? It integrated seamlessly with Android, had a generous free tier, and—let’s be honest—I wasn’t ready for anything more complex at that point. But it worked. And looking back, it was kind of impressive that it worked at all.&lt;/p&gt;

&lt;p&gt;Eventually, I added a section for browsing upcoming matches, with quick actions to start building betslips. And most importantly, &lt;strong&gt;live score notifications&lt;/strong&gt; were now in place.
The betslip lifecycle was finally complete.&lt;/p&gt;

&lt;p&gt;BetAssist had become a real product.&lt;/p&gt;

&lt;p&gt;And it was the best feeling in the world.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;growing-together&quot;&gt;Growing Together&lt;/h2&gt;

&lt;p&gt;With the app working, I couldn’t wait to share it with the world. I created a developer account on the Google Play Store and prepared for launch.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&quot;/assets/article_images/2025-06-16-betassist/screens.jpg&quot; alt=&quot;Original Screenshots&quot; /&gt;
  &lt;figcaption&gt;Original screenshots from the BetAssist app.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;I’ll spare you the gritty details (maybe another blog post someday), but publishing your first app is a trip. Keystore generation, signing builds, making screenshots, writing copy, creating privacy policies—it’s a lot.&lt;/p&gt;

&lt;p&gt;But eventually, &lt;strong&gt;BetAssist was live&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Seeing real people download and review something you built is electrifying. The download count ticked up. Comments started appearing. It wasn’t millions of users—but it was &lt;em&gt;mine&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Over the years, the app grew, and so did I. I added match predictions (basic regression models running on the device), partnered with websites, improved the design, and refactored endlessly as my coding skills matured.&lt;/p&gt;

&lt;p&gt;At one point, I even monetized the app. I added banners, and later a paid “Pro” version without ads. To my surprise, it actually brought in some money. Not a fortune, but enough to feel &lt;em&gt;real&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Eventually, I stopped the monetization. I wasn’t sure whether it was fully legal to earn revenue without a proper &lt;em&gt;partita IVA&lt;/em&gt; (Italian VAT number), and I didn’t want to risk it.&lt;/p&gt;

&lt;p&gt;But truthfully, the money was never the point.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;the-present&quot;&gt;The Present&lt;/h2&gt;

&lt;p&gt;Today, BetAssist is mostly in hibernation.&lt;/p&gt;

&lt;p&gt;The Heroku backend was shut down about a year ago. I think the app is still available on the Play Store, though only partially functional.&lt;/p&gt;

&lt;p&gt;BetAssist was never meant to be a business. It was my lab. It was where I learned to write real code, ship to real users, and fix real bugs.&lt;/p&gt;

&lt;p&gt;It was the first time I saw something go from an idea in my head to a thing in people’s hands. And I believe every developer needs that kind of project—one that belongs entirely to them.&lt;/p&gt;

&lt;p&gt;In the end, the most important feature of BetAssist wasn’t live scores, push notifications, or betting logic. It was &lt;strong&gt;momentum&lt;/strong&gt;—the creative spark it gave me, the confidence it built, the curiosity it unlocked.&lt;/p&gt;

&lt;p&gt;So if you’re a developer stuck in tutorials, wondering what to build next—just start. Solve your own problem. Scratch your own itch. It doesn’t have to be revolutionary. It just has to matter to &lt;em&gt;you&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Because sometimes, the best way to grow isn’t by reading more books. It’s by betting on yourself.&lt;/p&gt;

&lt;p&gt;And honestly? That’s a bet worth taking.&lt;/p&gt;
</description>
        <pubDate>Mon, 16 Jun 2025 00:00:00 +0000</pubDate>
        <link>https://riccardomaldini.it/blog/betassist/</link>
        <guid isPermaLink="true">https://riccardomaldini.it/blog/betassist/</guid>
        
        <category>projects</category>
        
        <category>java</category>
        
        <category>android</category>
        
        <category>sport</category>
        
      </item>
    
  </channel>
</rss>
