<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Event-Driven by Oskar Dudycz — articles]]></title><description><![CDATA[Practical articles, training, and consulting about Event Sourcing, event-driven architecture, CQRS, and software architecture.]]></description><link>https://event-driven.io</link><generator>GatsbyJS</generator><lastBuildDate>Thu, 08 Oct 2026 14:29:29 GMT</lastBuildDate><item><title><![CDATA[Open Source, a relic, a charity or still the thing?]]></title><description><![CDATA[A friend asked me recently whether there is any sense in doing Open Source now. My answer was the same as always. I see three options.…]]></description><link>https://event-driven.io/en/open-source-a-relict-a-charity-or/</link><guid isPermaLink="false">https://event-driven.io/en/open-source-a-relict-a-charity-or/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b322c1fb68f4cc9c6a674f2d1cc80755/4284b/2026-10-05-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIEBf/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAHOpFc5RP/EABkQAAIDAQAAAAAAAAAAAAAAAAABAgMSMf/aAAgBAQABBQJVKJbE4bZctKSP/8QAFREBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQMBAT8BJ//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EAB0QAAIBBAMAAAAAAAAAAAAAAAABIQIQERIxQVH/2gAIAQEABj8CUTkq8RyNG3dv/8QAGRABAAIDAAAAAAAAAAAAAAAAAQAhETFB/9oACAEBAAE/IQEZA3mINF6a3F6Qt1DM2iWJaSynk//aAAwDAQACAAMAAAAQ98//xAAWEQEBAQAAAAAAAAAAAAAAAAAAARH/2gAIAQMBAT8Qpr//xAAXEQEAAwAAAAAAAAAAAAAAAAAAAREh/9oACAECAQE/EJ1T/8QAHBAAAwEBAAMBAAAAAAAAAAAAAREhADFhcYHB/9oACAEBAAE/EAGp1xax6wtBE0ogvPc/MII/mUpkgaRRvzSLIuHimhZw2AWe7//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/b322c1fb68f4cc9c6a674f2d1cc80755/507e6/2026-10-05-cover.webp 200w,
/static/b322c1fb68f4cc9c6a674f2d1cc80755/28a80/2026-10-05-cover.webp 400w,
/static/b322c1fb68f4cc9c6a674f2d1cc80755/8d2ea/2026-10-05-cover.webp 800w,
/static/b322c1fb68f4cc9c6a674f2d1cc80755/68fc1/2026-10-05-cover.webp 1200w,
/static/b322c1fb68f4cc9c6a674f2d1cc80755/43d96/2026-10-05-cover.webp 1600w,
/static/b322c1fb68f4cc9c6a674f2d1cc80755/09625/2026-10-05-cover.webp 3000w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/b322c1fb68f4cc9c6a674f2d1cc80755/37402/2026-10-05-cover.jpg 200w,
/static/b322c1fb68f4cc9c6a674f2d1cc80755/4cda9/2026-10-05-cover.jpg 400w,
/static/b322c1fb68f4cc9c6a674f2d1cc80755/c60e9/2026-10-05-cover.jpg 800w,
/static/b322c1fb68f4cc9c6a674f2d1cc80755/6c738/2026-10-05-cover.jpg 1200w,
/static/b322c1fb68f4cc9c6a674f2d1cc80755/56dca/2026-10-05-cover.jpg 1600w,
/static/b322c1fb68f4cc9c6a674f2d1cc80755/4284b/2026-10-05-cover.jpg 3000w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/b322c1fb68f4cc9c6a674f2d1cc80755/c60e9/2026-10-05-cover.jpg&quot;
            alt=&quot;Collection of vintage items on shelves and floor - Free Photo on Unsplash&quot;
            title=&quot;Collection of vintage items on shelves and floor - Free Photo on Unsplash&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;A friend asked me recently whether there is any sense in doing Open Source now. My answer was the same as always. I see three options.&lt;/p&gt;
&lt;p&gt;1. You get money out of it.&lt;br&gt;
2. You’re dogfooding it in your work projects.&lt;br&gt;
3. You have fun doing it.&lt;/p&gt;
&lt;p&gt;If none of that is true, it makes no sense and never did. Best if there’s a combination of 1 or 2 with 3. And at least one of those options is a bare minimum.&lt;/p&gt;
&lt;p&gt;Doing OSS is mostly for fun; success stories of single people who succeeded financially through it are rare. Personally, even though some of my projects got wide adoption, I always struggled to make money from them to justify the effort. Unfortunately, OSS creators don’t get enough respect for their effort; with GenAI, that’s even lower. And I don’t think that’s good for the industry long-term, but that’s, in my opinion, the reality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So my advice would be to think about whether:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1. Your project has commercial potential.&lt;br&gt;
2. It can be turned into a product.&lt;br&gt;
3. There are people willing to pay for that.&lt;/p&gt;
&lt;p&gt;If yes, go closed-source and productise it. You can always open it later or make the source available to customers.&lt;/p&gt;
&lt;p&gt;This kind of goes back to the old days of shareware, etc. I don’t like that, but this is how it goes. I also need to learn from my own advice.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;„I’m just standing on the shoulders of giants”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;is a common phrase to show that you’re humble.&lt;/p&gt;
&lt;p&gt;In the Open Source, it is the opposite. In our industry, giant companies stand on the shoulders of a small group of passionate, regular folks.&lt;/p&gt;
&lt;p&gt;Many people believe that “someone is sponsoring those projects”, but when you think about “ok, but did I sponsor some?” then the answer is usually no.&lt;/p&gt;
&lt;p&gt;I have the luck to get some sponsorship. Not huge, but still. I’m calling that “Whiskey money”. I don’t drink a lot, but I could buy a bottle with that amount from time to time and could salute those good folks.&lt;/p&gt;
&lt;p&gt;It’s important to think of supporting OSS as due diligence, not gratitude. It’s ensuring that critical parts of your architecture will be maintained and continued. Essentially, that lowers your cost and risk.&lt;/p&gt;
&lt;p&gt;Yes, we can vibe code a lot these days, but do we want to take responsibility and get late-night calls if that part doesn’t work?&lt;/p&gt;
&lt;p&gt;So folks, check whether the OSS you’re using has support contracts, consulting or workshops, sponsorship, or paid add-ons. Don’t think of them as charity, but as a way to outsource work someone else can do cheaper and more reliably.&lt;/p&gt;
&lt;p&gt;Read also my older takes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how_to_start_with_open_source/&quot;&gt;How to get started with Open Source?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/why-open-source-isnt-always-fair/&quot;&gt;Why Open Source Isn’t Always Fair. Dual licenses explained&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/a_few_notes_on_running_open_source_project/&quot;&gt;A few notes on running open source project after Marten v6 release&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;Webinar with Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/i_am_no_longer_marten_maintainer/&quot;&gt;I’m no longer Marten maintainer&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/revolution_now/&quot;&gt;Revolution now!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. If you like my work, use my tools, and it helped you, you can do that through my GitHub sponsorship. I’ll salute you in the evening time! &lt;a href=&quot;https://github.com/sponsors/event-driven-io&quot;&gt;https://github.com/sponsors/event-driven-io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[We all wanted to be Netflix, who do we want to be next?]]></title><description><![CDATA[I recently realised that there’s an aspect of GenAI tooling that’s not spoken about much, and I think that’s important to where we are going…]]></description><link>https://event-driven.io/en/we-all-wanted-to-be-netflix-who-do/</link><guid isPermaLink="false">https://event-driven.io/en/we-all-wanted-to-be-netflix-who-do/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b5e1fe63d80c89f8d4697f8e082af24d/16038/2026-09-14-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAEEA//EABYBAQEBAAAAAAAAAAAAAAAAAAECA//aAAwDAQACEAMQAAABoUrXUZWf/8QAGRAAAwADAAAAAAAAAAAAAAAAAAECESEx/9oACAEBAAEFAlEmJY4RBHa0/wD/xAAYEQACAwAAAAAAAAAAAAAAAAAAAQIREv/aAAgBAwEBPwFRVGT/xAAYEQACAwAAAAAAAAAAAAAAAAAAAQIREv/aAAgBAgEBPwFydmz/xAAUEAEAAAAAAAAAAAAAAAAAAAAg/9oACAEBAAY/Al//xAAaEAACAgMAAAAAAAAAAAAAAAABIQARMUFR/9oACAEBAAE/IaBwhqDcpIdltTExxvpgpCE//9oADAMBAAIAAwAAABBD/wD/xAAWEQEBAQAAAAAAAAAAAAAAAAABABH/2gAIAQMBAT8QUls3/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERYf/aAAgBAgEBPxCRGPB//8QAGhABAQEBAAMAAAAAAAAAAAAAAREAITFBYf/aAAgBAQABPxABilqoY/d1kUKHlKuRURLkoqosb75m0lgdxQQAgcN//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/b5e1fe63d80c89f8d4697f8e082af24d/507e6/2026-09-14-cover.webp 200w,
/static/b5e1fe63d80c89f8d4697f8e082af24d/28a80/2026-09-14-cover.webp 400w,
/static/b5e1fe63d80c89f8d4697f8e082af24d/8d2ea/2026-09-14-cover.webp 800w,
/static/b5e1fe63d80c89f8d4697f8e082af24d/e00f7/2026-09-14-cover.webp 1000w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/b5e1fe63d80c89f8d4697f8e082af24d/37402/2026-09-14-cover.jpg 200w,
/static/b5e1fe63d80c89f8d4697f8e082af24d/4cda9/2026-09-14-cover.jpg 400w,
/static/b5e1fe63d80c89f8d4697f8e082af24d/c60e9/2026-09-14-cover.jpg 800w,
/static/b5e1fe63d80c89f8d4697f8e082af24d/16038/2026-09-14-cover.jpg 1000w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/b5e1fe63d80c89f8d4697f8e082af24d/c60e9/2026-09-14-cover.jpg&quot;
            alt=&quot;Teenage Mutant Ninja Turtles: 10 Things You Need To Know About Krang&quot;
            title=&quot;Teenage Mutant Ninja Turtles: 10 Things You Need To Know About Krang&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I recently realised that there’s an aspect of GenAI tooling that’s not spoken about much&lt;/strong&gt;, and I think that’s important to where we are going as an industry. Before I get into the details, let me start with some background.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For a long time, our industry was (over) focused on following big tech’s practices.&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;“Let’s be like Netflix!”&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;- that was a common phrase.&lt;/strong&gt; Even if we weren’t saying it out loud, our discussions, directions, design, and tooling choices said it.&lt;/p&gt;
&lt;p&gt;Of course, there was always a group of people saying: &lt;em&gt;“well, it depends; we’re not Netflix, aye? So maybe we do stuff simpler?”&lt;/em&gt;. Still, even they were saying stuff like &lt;em&gt;“I don’t like microservices, but there are valid scenarios for it”&lt;/em&gt;. The soft power of big tech was strong.&lt;/p&gt;
&lt;p&gt;To some degree, blind copying of their practices led to overengineering, over-staffing, and overly complex operational stuff (hey, Kubernetes!). It even impacted recruitment and staffing with weird practices like LeetCode or System Design interviews.&lt;/p&gt;
&lt;p&gt;Yet, undeniably, it also had a good impact. Cloud computing commoditization enabled companies without enough internal skills to build better, more secure products. It also enabled models like pay-as-you-go. Plus, those edgy problems that big tech had ended up with useful, mature open-source projects.&lt;/p&gt;
&lt;p&gt;Of course, those “insightful blog articles” included a lot of marketing. They shaped our industry narratives. Companies wrote about the problems they had, which were mostly problems of scale, and those became the problems everyone talked about. They were success stories. Even when they were doing post mortems, or other fuck ups, they ofc presented it as &lt;em&gt;“hey, but we eventually to solve it, so we’re good right?”.&lt;/em&gt; Also, some practices attributed to them weren’t actually used. Google didn’t use Angular; Spotify didn’t use the Spotify Model, etc. Still they carried a set of good intentions: explaining how stuff can be done bigger, faster, or just a bit of bragging on what they do. On its own, I consider that a good thing.&lt;/p&gt;
&lt;p&gt;It was also a good thing because of the form it took. A blog post is an argument. You can read it, disagree with it, take the idea and drop the implementation, or tell your team it solves a problem you don’t have. It caused discussions and new ideas. Sometimes backslashes, sometimes maturity. Those ideas had a positive impact, because there was something on the table to point to. We could say, that’s what we’re going to try, or we could say: no, it’s not for our scale. That led to answering questions of why. Of course, many didn’t ask why and just blindly followed, causing more issues than benefits.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now, here’s what’s bothering me.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We all wanted to be like Netflix. Do we want all of our development processes and practices to be like OpenAI’s or Anthropic’s?&lt;/p&gt;
&lt;p&gt;Some would say &lt;em&gt;“yeah, why not, they’re big and successful”&lt;/em&gt; (&lt;a href=&quot;https://www.wheresyoured.at/premium-the-haters-guide-to-circular-financing-part-one/&quot;&gt;whether they are is a different story&lt;/a&gt;), but…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But what you get is not an OpenAI or Anthropic way. It’s the way they think engineers like us should, or&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;“could”&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;, do our work.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I don’t have insider info from those companies, so this is more my bet: knowing they’re the “Ivy League” of devs, my bet is they don’t think we’re capable of doing it all right. They may optimise the solutions and the engineering workflow for us at the “average Joe engineer” level.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is a much different dynamic than we had with “Let’s be like Netflix”.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Copying Netflix meant copying someone who had solved (or at least claimed to solve) a problem in a specific context. Of course, Netflix and likes were selling something too. Those posts were recruitment, reputation, sometimes a push for their own open source. But their revenue didn’t depend on whether we adopted any of it, and the practice travelled together with the reasoning, because the reasoning was most of what they published.&lt;/p&gt;
&lt;p&gt;With the LLM tooling, the practice comes embedded in a product, the reasoning stays inside the vendor, and that vendor’s revenue depends on how much we use it. We can’t read the thinking the way we could read a blog post. We can’t measure thre consequences.&lt;/p&gt;
&lt;p&gt;I’m not even talking about coding. Many people let LLM tooling to break down problem, define what counts as done. And those decisions were always the slow part, and they are where development cost, quality and delivery time actually come from. Nobody outside our industry cares how we code; they care about those three. So when the deciding moves into the tool, what stays on our side? If we’re betting that all can be outsourced, then what’s our role?&lt;/p&gt;
&lt;p&gt;With all this generated code, specs, and whatever else you get from Genie, you’re commoditising the knowledge harvested (or, um, stolen) by OpenAI, Anthropic and Google. &lt;em&gt;That can be fine if you’&lt;/em&gt;re below average in your engineering practices or you’re lacking certain skills. I’ve seen teams that, thanks to that, were able to survive with an inherited maze created by other people. For plenty of teams and plenty of tasks, the average answer is better than what would have been shipped otherwise. We already see obvious choices like React for frontend and PostgreSQL for the database.&lt;/p&gt;
&lt;p&gt;I’m all for choosing a boring tech stack. As a default choice, the issue is whether that’s the only choice or if we’re making our solution simplistic instead of simple. Or complicated instead of complex.&lt;/p&gt;
&lt;p&gt;LLM training is as biased as the people who trained it, through weights, training data and whatever tuning sits on top. Same with the harness, or even more, as it’s the code someone put there. &lt;a href=&quot;/en/computer_says_no_we_may_have_an_issue_with_ai_soon/&quot;&gt;I wrote about it some time ago in the pre-LLM AI era.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GenAI tools are statistical parrots; they repeat the most probable answer, which most of the time means either mediocre or good, decided by the person who set the weights during training. Training is always subjective. If you ship one default to everybody, you tune it for the widest audience, which lands in roughly the same spot.&lt;/p&gt;
&lt;p&gt;Some can say, that we could use open source models instead. Personally, I don’t consider open models or custom workflows per se as something changing this dynamic. I think that’s better, but not a solution. They change who holds the defaults, not the fact that defaults are now doing the arguing for us. Plus the cost of servers is still super high compared to the value.&lt;/p&gt;
&lt;p&gt;With GenAI tooling we’re not only getting data and ideas on how to solve our design challenge or fix a bug. We’re also getting the workflow we’re forced to work with. This is impacting us from both angles. We ask and work on the design with this tooling and also let it do the final work. Harness is not only for getting and interpreting your needs through a model. It actually also harnesses your code and the design you get.&lt;/p&gt;
&lt;p&gt;And now the question is: who’s putting the leash? Are we on our chosen design to ensure it’s as we want. Or is the GenAI harness putting the leash on us to provide the design it’s capable of or fine-tuned for?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/en/vibing_harness_and_ooda_loops/&quot;&gt;I wrote about my vision of the harness&lt;/a&gt;. I see it more as a way to ensure our process is reproducible and follows our intention: tests, automation, traces, a setup. We design it to be able to iterate fast, but not just for going fast, but to get quickly feedback loop and validate our assumptions. Going fast, in my opinion, shouldn’t be ever a goal on it’s own.&lt;/p&gt;
&lt;p&gt;The vendor harness is a different animal. It’s created by vendor with the masses in mind. It has its own system prompt and tool definitions; it decides what gets pulled into context and what doesn’t, when the agent decides it’s done, and whether it asks you something or just guesses. It reflects the vision of authors on how the process look like. It’s not optimised for our approach; we need to fit into it if we want to get the benefit. Just like with application frameworks: we can benefit as long as our approach aligns with the framework’s author’s vision; otherwise, we’ll struggle by not doing things idiomatically. The challenge with LLMs is that we don’t necessarily know the idioms, since big vendors aren’t sharing them. They only provide guides as they see us working with their tools. And those guides don’t have to be optimised for doing things right, but for using those tools more. And we never know, as internals are hidden.&lt;/p&gt;
&lt;p&gt;So, we don’t know the weights or vendors training data; we don’t know how those harnesses work internally, and even if we did, they could change at any time. Between one release and the next, the tool making decisions inside our workflow can start behaving differently, and you won’t necessarily be told.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m not saying to drop using them. On my own I’m using those tools everyday, with mixed results.&lt;/strong&gt; Till this day what works best for me is the process I described in &lt;a href=&quot;/en/interactive_rubber_ducking_with_gen_ai/&quot;&gt;Interactive Rubber Ducking with GenAI&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I’m using GenAI tool to ask me questions about my idea,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then letting me answer and make decision without making them for me,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then summarising into spec, that I work on,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Doing the work on my direction and review.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of the workflow, automated multiagents etc. didn’t work for me in a proper, reliable way.&lt;/p&gt;
&lt;p&gt;I also believe that’s also more aligned with how LLMs are build, they’re great in text processing. They’re not thinking, they’re just processing.&lt;/p&gt;
&lt;p&gt;What to do about it? The first step is to realise it we have a problem, but frankly, I don’t have clear solution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I don’t know.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I think that’s also why we have blogs, to share, put a seed, have a discussion. It’s fine to just don’t know all answers.&lt;/p&gt;
&lt;p&gt;What I know is that we shouldn’t be outsourcing our thinking, as that’s the role of engineer, if we lose it, then indeed our profession is doomed. Do we want to truly bet on that?&lt;/p&gt;
&lt;p&gt;I also believe that’s a more important discussion than whether we should code or not, or what’s the latest and greatest model today at 12:37 AM.&lt;/p&gt;
&lt;p&gt;Thoughts?&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. You may be also interested on my other articles on GenAI&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/vibing_harness_and_ooda_loops/&quot;&gt;Vibing, Harness and OODA loop&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/requiem-for-a-10x-engineer-dream/&quot;&gt;Requiem for a 10x Engineer Dream&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/the_end_of_coding_wrong_question/&quot;&gt;The End of Coding? Wrong Question&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/interactive_rubber_ducking_with_gen_ai/&quot;&gt;Interactive Rubber Ducking with GenAI&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/computer_says_no_we_may_have_an_issue_with_ai_soon/&quot;&gt;Computer says no! Why we might have an issue with Artificial Intelligence soon&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Avoid Duke Nukem Forever Mode]]></title><description><![CDATA[How old are you? I’m Duke Nukem 3D old. Not in the sense that I was born in 1996, but that it came out when I was the right age. Being 1…]]></description><link>https://event-driven.io/en/avoid-duke-nukem-forever-mode/</link><guid isPermaLink="false">https://event-driven.io/en/avoid-duke-nukem-forever-mode/</guid><content:encoded>&lt;p&gt;How old are you? I’m Duke Nukem 3D old.&lt;/p&gt;
&lt;p&gt;Not in the sense that I was born in 1996, but that it came out when I was the right age. Being 11 years old was the right age to laugh hard at Duke’s jokes. Duke Nukem 3D was the first shooter game that asked the question: Why so serious? It was a unique game, as almost none of the earlier (and future) blockbusters won the audience with a lame sense of humour.&lt;/p&gt;
&lt;p&gt;It was a great hit. Even though it wasn’t actually 3D, more like 2.5D. It doesn’t have the best graphics, but it’s good enough, especially when paired with the creativity of the plot, scenarios, and gameplay. It sold 3.5 million copies.&lt;/p&gt;
&lt;p&gt;It was such a success that the year after, the 3D Realms game studio co-founder George Broussard announced the sequel: Duke Nukem Forever. He expected to deliver it as a Christmas gift in 1998. It appeared to be quite a bold statement, since the game was released in June 2011. Yes, 13 years later than promised. What happened?&lt;/p&gt;
&lt;p&gt;It’s a long story; look here: an almost hour-long video on that:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/dfV4rI_gR1g?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;So let me be shorter and give you an even shorter version than the &lt;a href=&quot;https://en.wikipedia.org/wiki/Development_of_Duke_Nukem_Forever&quot;&gt;Wikipedia Page&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;They realised that the 2.5D engine was already obsolete, and in 1.5 years it’ll be even more so, so they bought a license for the Quake 2 engine. For 0.5 million bucks. A lot, keeping in mind that they got only 0.6 million bucks in advance from their publisher for the new game.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They showed the nicely looking trailer; critics were happy, but creators wanted more. They wanted something better, and after 14months of investment, they bought the latest-the-greatest at that time Unreal Engine, because why not. Just half a year till the promised Christmas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Not surprisingly, changing the engine while building the &lt;del&gt;car&lt;/del&gt; game wasn’t the best move for the deadlines, and the game was still unfinished and in moving parts, even more so than it was. So, in 1999, they decided to change the engine again: to the new Unreal Engine, which had multiplayer capabilities. Yes, they decided to add multiplayer to an initially single-player game. A bit of scope creep.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They worked on it and even showed something that looked nice in 2001, which, as Broussard said, was actually smoke &amp;#x26; mirrors. Take-Two Interactive bought them and got into a fight with the new owners, claiming it would be done when it was done.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In 2004, they switched engines again, this time to Doom 3.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In 2006, nothing was working yet; the team was “basically pulling it all together and trying to make it fun”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In 2009, they laid off the staff, downsizing the company. Take-Two filed a lawsuit against 3D Realms over their failure to complete Duke Nukem Forever, citing $12 million paid to Infogrames in 2000 for the publishing rights&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nine ex-employees continued development throughout 2009 from their homes. Eventually, they created Triptych Games, an independent studio.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Somehow, they managed to complete something and release it in 2011.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The outcome? The game was a total flop, nowhere near the success of Duke Nukem 3D.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Fun fact: until 2024, Duke Nukem Forever held the Guinness World Record for the longest development of a video game, at 14 years and 44 days.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ok, why am I writing about it in the Architecture Weekly?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because I’m also the one to blame. Not for the Duke Nukem delay, but for falling into the same trap. “Do as we tell, not as we do”.&lt;/p&gt;
&lt;p&gt;When &lt;a href=&quot;/en/introducing_emmett/&quot;&gt;I started Emmett,&lt;/a&gt; I went with the lean approach. I started by delivering the API for building Event-Driven applications. The first version didn’t even have any event store implementation.&lt;/p&gt;
&lt;p&gt;I showed that to the rest of the world, and I quickly got feedback from folks who wanted to use it and said they were happy with what they saw. So I added the storage I already had &lt;a href=&quot;https://github.com/oskardudycz/EventSourcing.NodeJS&quot;&gt;in my samples: EventStoreDB storage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then &lt;a href=&quot;/en/introducting_pongo/&quot;&gt;I added, for fun, Pongo&lt;/a&gt; - a MongoDB-compliant library that allows you to use PostgreSQL as a document database.&lt;/p&gt;
&lt;p&gt;And I decided to add Dumbo, a shared package that will be responsible for connection management, SQL queries, etc. and use it to build the PostgreSQL event store.&lt;/p&gt;
&lt;p&gt;That worked well; Pongo made it to Hacker News’ front page. Emmett, PostgreSQL is probably the best Node.js PostgreSQL-based event store (not much competition, to be fair). People are using it in production. I haven’t had much success making income from it yet, but at least adoption is growing steadily.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then I decided it was time to make it production-ready.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It was 1.5 years ago.&lt;/p&gt;
&lt;p&gt;And yes, it’s production-ready, but the new official versions are still unreleased: 0.43.0 of Emmett and 0.17.0 of Pongo.&lt;/p&gt;
&lt;p&gt;Why? For various reasons, some personal ones, but focusing on the development stuff:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I entirely rewrote connection management and implemented my own connection pooling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Decided to add more database types for Emmett and Pongo: SQLite, &lt;a href=&quot;/en/cloudflare_d1_transactions_and_tradeoffs/&quot;&gt;Cloudflare D1&lt;/a&gt;. Fun fact: I used the sqlite3 Node.js driver, which has since become unmaintained.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;That required making Dumbo drivers pluggable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I decided to add OpenTelemetry.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I added resiliency and batching for processor implementations. Essentially rewrote the whole async processing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I added &lt;a href=&quot;/en/workflow-engine-design-proposal-tell/&quot;&gt;Workflows&lt;/a&gt; for business process coordination.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And &lt;a href=&quot;https://github.com/event-driven-io/emmett/pulls?q=is%3Apr+is%3Aclosed+milestone%3A0.43.0&quot;&gt;many&lt;/a&gt;, &lt;a href=&quot;https://github.com/event-driven-io/Pongo/pulls?q=is%3Apr+is%3Aclosed+milestone%3A0.17.0&quot;&gt;many&lt;/a&gt; other stuff.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Essentially, I locked myself into the big, great bang release. I also fell into:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/gAjR4_CbPpQ?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Which is fine, as those tools are great: Emmett 0.43.0 and Pongo 0.17.0 betas are totally different products: richer in features, more performant, resilient, etc.&lt;/p&gt;
&lt;p&gt;Still, if you fell into that, then it’s easy to justify yourself and persuade yourself that there’s “one more thing needed” and “if I waited so long, then we could wait a bit more”.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Luckily, I didn’t stop the feedback loop&lt;/strong&gt;. I was publishing alphas and betas quite often. I treated this as something I could release as-is if I had to. So didn’t turn upside down. Some people even started to use those betas in prod. Some couldn’t, of course, because of compliance yada yada.&lt;/p&gt;
&lt;p&gt;Still, I should have known that better. In past, I either fell into the same miserable place and promised myself not to fall into that again. Well, I lied.&lt;/p&gt;
&lt;p&gt;The worst thing is that this is mostly sitting in my own head. People would prefer to get more frequent updates. Of course, one reason was that I wanted to group breaking changes to make this easier for folks, but if you group too many, people won’t migrate. There are other options, and I already smoothed the migration story.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s also a warning for you.&lt;/strong&gt; With GenAI tools, it’s super easy to fall into analysis paralysis or refucktoring. It’s also easy to fall into the waterfall-style plan. Having too many options, too many ideas, even if they’re great doesn’t help.&lt;/p&gt;
&lt;p&gt;Yes, if you’re motivated enough, you can achieve anything, but…&lt;/p&gt;
&lt;p&gt;…but you cannot achieve everything. The time and focus we have isn’t infinite.&lt;/p&gt;
&lt;p&gt;It’s much better to stay focused, have one streamlined workflow, deliver continuously, and handle breaking changes. And most importantly…&lt;/p&gt;
&lt;p&gt;Focusing on the feedback loop.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So don’t be like George Broussard; don’t be like me and avoid Duke Nukem Forever mode.&lt;/strong&gt; It’s oh-too-easy to fall into it. And consequences can be severe.&lt;/p&gt;
&lt;p&gt;Just because you can do something doesn’t mean you have to. We need to pick wisely.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Start Small, Grow Big: how to pick the first feature for Event Sourcing]]></title><description><![CDATA[Let’s say we did the hard part. We read up on Event Sourcing, even here on my blog; we did a modelling workshop, we got the nods around the…]]></description><link>https://event-driven.io/en/start-small-grow-big-how-to-pick/</link><guid isPermaLink="false">https://event-driven.io/en/start-small-grow-big-how-to-pick/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 612px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6c32858fa24e2313e6c0898f71a03bd2/a7d3e/2026-08-31-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAIBBAX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgP/2gAMAwEAAhADEAAAAdGsj5CEkH//xAAbEAACAgMBAAAAAAAAAAAAAAABAgMiAAQSMv/aAAgBAQABBQKU8xu1FtnohFcvrqT/AP/EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAEDAQE/AWf/xAAVEQEBAAAAAAAAAAAAAAAAAAAQQv/aAAgBAgEBPwGT/8QAHBABAAEEAwAAAAAAAAAAAAAAAQACEDFhESFB/9oACAEBAAY/Atwqc+FincBxOuSf/8QAGhABAAMAAwAAAAAAAAAAAAAAAQARITFBYf/aAAgBAQABPyEQbNBGv6vhxKCwTwyU2meIAL05mtAdDP/aAAwDAQACAAMAAAAQxC//xAAXEQADAQAAAAAAAAAAAAAAAAAAARFh/9oACAEDAQE/EEamkP/EABYRAQEBAAAAAAAAAAAAAAAAAAEAEf/aAAgBAgEBPxBSaF//xAAcEAACAgMBAQAAAAAAAAAAAAABEQAhMUFhcaH/2gAIAQEAAT8QDA10OnsQo6DMMCtuMVsokQHxymwO7YlCGGtATThLgYfR7P/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/6c32858fa24e2313e6c0898f71a03bd2/507e6/2026-08-31-cover.webp 200w,
/static/6c32858fa24e2313e6c0898f71a03bd2/28a80/2026-08-31-cover.webp 400w,
/static/6c32858fa24e2313e6c0898f71a03bd2/1d2dc/2026-08-31-cover.webp 612w&quot;
              sizes=&quot;(max-width: 612px) 100vw, 612px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/6c32858fa24e2313e6c0898f71a03bd2/37402/2026-08-31-cover.jpg 200w,
/static/6c32858fa24e2313e6c0898f71a03bd2/4cda9/2026-08-31-cover.jpg 400w,
/static/6c32858fa24e2313e6c0898f71a03bd2/a7d3e/2026-08-31-cover.jpg 612w&quot;
            sizes=&quot;(max-width: 612px) 100vw, 612px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/6c32858fa24e2313e6c0898f71a03bd2/a7d3e/2026-08-31-cover.jpg&quot;
            alt=&quot;Girl Picking Cherries Stock Photo - Download Image Now - Cherry, Picking -  Harvesting, Girls - iStock&quot;
            title=&quot;Girl Picking Cherries Stock Photo - Download Image Now - Cherry, Picking -  Harvesting, Girls - iStock&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Let’s say we did the hard part. We read up on Event Sourcing, even here on my blog; &lt;a href=&quot;/en/training/&quot;&gt;we did a modelling workshop&lt;/a&gt;, we got the nods around the table, and someone finally said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Fine, let’s try it somewhere”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then, after the cheerful atmosphere, the follow-up arrives:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“OK, but where?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We do some head-scratching, pick something from the top of our backlog without further consideration. Some people say we should event-source everything so we can select anything, right?&lt;/p&gt;
&lt;p&gt;Yes, you can use Event Sourcing everywhere, but you don’t have to, and it won’t add enough value over the familiar approach to justify the learning curve. It’s not that hard; it really doesn’t, but well, if you need to learn both the new approach and new tooling, and persuade colleagues, then it’s better to pick wisely.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;/en/when_not_to_use_event_sourcing/&quot;&gt;When not to use Event Sourcing?&lt;/a&gt; I ended with a suggestion: Start Small, Grow Big. Start with functionality that won’t hurt you if it fails, deploy it to production, learn from it, then rinse and repeat.&lt;/p&gt;
&lt;p&gt;That sounds like nice advice, doesn’t it? But it’s easy to write and harder to apply.&lt;/p&gt;
&lt;h2 id=&quot;picking-the-right-problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#picking-the-right-problem&quot; aria-label=&quot;picking the right problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Picking the right problem&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;“Small” is a word everyone agrees with and nobody defines it the same way. It’s in the same category as the “good enough” term. How small is good enough? How small is too small or too big? If we pick something so small, we can get a wrong, simplistic impression. If we pick something too complex (like our core domain), we may get into trouble, as…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Starting in the core domain is the most tempting mistake&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Our core domain usually sounds like the best fit for Event Sourcing. That’s where the most complex logic happens. That’s where the most sophisticated business processes are. In theory, that’s where extended business observability pays off. Choosing based on the business domain fit would sound reasonable, aye?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f2e6535d05d80158fbfecb16e39fb919/c60e9/image-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAwD/xAAVAQEBAAAAAAAAAAAAAAAAAAACA//aAAwDAQACEAMQAAABAnp0GaR//8QAGhABAQACAwAAAAAAAAAAAAAAAQIAERIhMv/aAAgBAQABBQKUrLJwer81JxA1/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAEDAQE/AZGP/8QAFhEBAQEAAAAAAAAAAAAAAAAAABES/9oACAECAQE/AdK//8QAGxAAAgEFAAAAAAAAAAAAAAAAABECEBIhQWL/2gAIAQEABj8CuR0bIjWXT//EABoQAQACAwEAAAAAAAAAAAAAAAEAESExUYH/2gAIAQEAAT8hoQw5DXVUVE2wunrDTG5rrJmONk//2gAMAwEAAgADAAAAEOA//8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERQf/aAAgBAwEBPxBA50//xAAXEQADAQAAAAAAAAAAAAAAAAAAARFB/9oACAECAQE/EGFWH//EABwQAQADAQEAAwAAAAAAAAAAAAEAESFBMVFhkf/aAAgBAQABPxDAaha8rOTFXdCXPjyKGBxYBdhXaOvs+GQ/EZLGjv1P/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/f2e6535d05d80158fbfecb16e39fb919/507e6/image-2.webp 200w,
/static/f2e6535d05d80158fbfecb16e39fb919/28a80/image-2.webp 400w,
/static/f2e6535d05d80158fbfecb16e39fb919/8d2ea/image-2.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/f2e6535d05d80158fbfecb16e39fb919/37402/image-2.jpg 200w,
/static/f2e6535d05d80158fbfecb16e39fb919/4cda9/image-2.jpg 400w,
/static/f2e6535d05d80158fbfecb16e39fb919/c60e9/image-2.jpg 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/f2e6535d05d80158fbfecb16e39fb919/c60e9/image-2.jpg&quot;
            alt=&quot;Well Yes, But Actually No - Meming Wiki&quot;
            title=&quot;Well Yes, But Actually No - Meming Wiki&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Starting a new pattern in the core domain means doing three hard things at once:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;fighting the complexity of a domain we don’t understand yet,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;learning the practice and the patterns,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and learning the tooling.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each is a real burden on its own. If we join them together, then we get the triple-size burden (or more). When we start getting issues (and we will), we can’t tell which of the three is currently biting us. Something breaks, and we can’t say whether we modelled the process badly, applied the pattern badly, or hit a library limitation.&lt;/p&gt;
&lt;p&gt;We make the most important decisions when we’re the dumbest, and here we’d manage to be dumb in three directions at the same time. Then the blame game starts, and we choose the easy excuse:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Event Sourcing didn’t work for us.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or worse:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Event Sourcing is too hard.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When we onboard a project with a new approach, we’re betting our credibility. And that’s a risky bet. If we do it wrongly, then even if next time we come up with the right choice, our colleagues can say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Oskar, do you remember, how did it end up the last time you wanted to introduce Event Sourcing? Yeah, then how about no?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’ve been on the other side of this. I rejected using Event Sourcing in a project where the domain fit well, as I was the only person familiar with it and we had teams spread across three countries. Saying no was cheaper than saying yes and becoming the single point of knowledge for everyone.&lt;/p&gt;
&lt;p&gt;That’s also what &lt;a href=&quot;https://calendly.com/oskar-dudycz/consulting&quot;&gt;I advised my consulting clients&lt;/a&gt; several times when they asked me to review their idea of applying Event Sourcing to their projects. Rarely was their business use case the biggest issue. More often the selected business case complexity compared to their stage in the learning journey.&lt;/p&gt;
&lt;h2 id=&quot;small-doesnt-mean-unimportant&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#small-doesnt-mean-unimportant&quot; aria-label=&quot;small doesnt mean unimportant permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Small doesn’t mean unimportant&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;In the &lt;a href=&quot;/en/when_not_to_use_event_sourcing/&quot;&gt;”When not to use Event Sourcing?” article&lt;/a&gt;, I suggested starting with non-important business functionality. That can be interpreted differently than I meant, so let me correct myself.&lt;/p&gt;
&lt;p&gt;What we want is functionality that’s &lt;em&gt;low-consequence-if-wrong&lt;/em&gt;, which is different from &lt;em&gt;noone-cares-about-it&lt;/em&gt;. Unimportant features usually have no business decisions in them, and a feature with no decisions &lt;a href=&quot;/en/anti-patterns/&quot;&gt;is not a great fit for Event Sourcing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If it’s crude, we may come up with events like &lt;em&gt;SomethingCreated&lt;/em&gt;, &lt;em&gt;SomethingUpdated&lt;/em&gt;, and &lt;em&gt;SomethingDeleted&lt;/em&gt;, produce a lot of boilerplate, and conclude that Event Sourcing brings useless ceremony. When picking a trivial use case, we typically end up with &lt;a href=&quot;/en/state-obsession/&quot;&gt;State Obsession&lt;/a&gt; or &lt;a href=&quot;/en/property-sourcing/&quot;&gt;Property Sourcing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I was a victim of that. I worked on a project with a dedicated configuration service. Mostly classical crudish dictionaries, but all integration ran through events. The amount of repetitive code was enormous. If you pick such a choice, you’ll only learn that choosing a bad fit for Event Sourcing is a bad fit for Event Sourcing. Or conclude that Event Sourcing is stupid. Or both.&lt;/p&gt;
&lt;p&gt;The feature we pick has to be real. Simple, but real. A colleague depends on it, it has a business name, it holds real data, and someone complains when it breaks. A demo shopping cart persuades nobody, including ourselves, because everyone knows demos work. It just needs to be a feature where being wrong for a day means somebody fixes it by hand and no money is lost.&lt;/p&gt;
&lt;p&gt;One thing to rule out before we go looking. Collecting events from several sources and assembling them into a read model isn’t Event Sourcing. It’s useful, and I’ve written about doing it well when events arrive out of order in &lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;Dealing with Race Conditions in Event-Driven Architecture&lt;/a&gt;, but if all we do is gather data and display it, we’re doing event-driven integration.&lt;/p&gt;
&lt;p&gt;I wrote a whole article on why the distinction matters: &lt;a href=&quot;/en/event_streaming_is_not_event_sourcing/&quot;&gt;Event Streaming is not Event Sourcing!&lt;/a&gt;. What makes it Event Sourcing is making decisions. We treat our events as the source of truth by: reading them, &lt;a href=&quot;/en/how_to_get_the_current_entity_state_in_event_sourcing/&quot;&gt;building state from them&lt;/a&gt;, and making a decision that produces a new event. In that race conditions article, the interesting moment is when the projection stops being a projection and becomes a workflow, because it started deciding something.&lt;/p&gt;
&lt;p&gt;Audit needs are the same story. Event stores make auditing easier, and a proper audit log is more work than appending events, which I covered in &lt;a href=&quot;/en/audit_log_event_sourcing/&quot;&gt;Is the audit log a proper architecture driver for Event Sourcing?&lt;/a&gt;. If the only reason for our pilot is “we’d like history”, we’ll build history and learn nothing about write models.&lt;/p&gt;
&lt;h2 id=&quot;selection-checklist&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#selection-checklist&quot; aria-label=&quot;selection checklist permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Selection checklist&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;OK, let me give a rough checklist to make my rambling more actionable.&lt;/p&gt;
&lt;p&gt;Let’s start with the business use case selection:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Who owns the process lifecycle?&lt;/strong&gt; If the lifecycle lives in another system and we watch it, we’re building an anti-corruption layer. Events from outside are rumours until we interpret them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Do we produce our own events, or only consume other people’s?&lt;/strong&gt; If nothing new comes out of the module, we’re building a read model and calling it Event Sourcing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Can a command be rejected for a business reason?&lt;/strong&gt; Database timeouts don’t count. I mean rules like “this was already approved, so it can’t be changed anymore”. If nothing can be refused, we have no business logic, no decision-making, and no reason for Event Sourcing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Is the decision a business rule or a retry policy?&lt;/strong&gt; Retry, backoff and dead-lettering belong to our messaging tool. They’re technical concepts with business consequences, but not a real business decision.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Could we run an EventStorming session on it with a non-technical colleague?&lt;/strong&gt; It’s much easier if we can collaborate with business people to get the domain knowledge from them. We can still do it on our own, but then we risk inventing the business scenario. If the events have no real business names, there’s no domain to model, and we lose the shared understanding that makes events valuable in the first place.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Does the stream end?&lt;/strong&gt; Short streams keep event stores fast and versioning manageable. A pilot on an unbounded stream teaches that lesson the painful way.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then, can we afford to be wrong?&lt;/p&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Is it off the critical path?&lt;/strong&gt; Nobody should be waiting on our learning curve to hit a release date.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What happens if it’s wrong for a day?&lt;/strong&gt; If the answer involves money, safety or a regulator, it’s the wrong pilot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Could we walk it back in a week?&lt;/strong&gt; If the retreat is a project of its own, we picked something too big.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Is it small enough to do slightly rogue?&lt;/strong&gt; If we need a formal decision before we can start, we’re back to arguing instead of showing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Can the event store we choose run on storage we already operate?&lt;/strong&gt; A new database means a conversation with the platform team, and that’s where most pilots die.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then, will we actually learn something?&lt;/p&gt;
&lt;ol start=&quot;12&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Does it fit inside a single module?&lt;/strong&gt; We want eventual consistency and idempotency, and we don’t want clock skew and cross-queue ordering in the same week.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Is any part of it asynchronous?&lt;/strong&gt; Waiting on an external system counts, and so does a timeout that fires on its own. Testing the async approach isn’t entirely about Event Sourcing as a pattern, but about checking how our tooling behaves and how we can reason about the challenges that will eventually land.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Is it real?&lt;/strong&gt; Someone depends on it, it has a business name, it holds real data, and somebody complains when it breaks. Of course, we should limit the risk, but we shouldn’t also pick features that no one cares about, because then we won’t run it for real and get proper learning from production usage.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;bad-picks&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#bad-picks&quot; aria-label=&quot;bad picks permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Bad picks&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;What are the examples of bad picks? No problem, let me walk you through three examples.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Email notification dispatch looks promising.&lt;/strong&gt; Sent, delivered, bounced, opened is a state machine with a decent vocabulary, except the state machine belongs to the mail provider and we’re only watching it. The decisions we’d implement are retry and escalation, which our broker already does, so it’s a technical concept. We’re not making decisions; we’re just handling side effects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Import and batch job processing looks promising too.&lt;/strong&gt; Started, chunk processed, failed, retried, completed. No domain expert will attend a meeting about ChunkProcessed, and retry-versus-abort is infrastructure policy. Imports deserve explicit events when we migrate relational data, as I described in &lt;a href=&quot;/en/the_end_is_near_for_crud_data/&quot;&gt;The end is near for CRUD data&lt;/a&gt;, but they’re not a pilot. So same issue as before.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A payment verification module looks the most promising of all.&lt;/strong&gt; Before a card payment goes through, several checks run at once: is this likely to be fraud, how risky is this customer, is the shop still within its daily limit. Each check runs in a different system and answers at its own pace, sometimes in the wrong order, so the fraud answer can land before we’ve even heard that the payment exists. Somebody has to collect those answers in one place and show whether verification is finished. Real vocabulary: a lifecycle that ends, out-of-order events on day one.&lt;/p&gt;
&lt;p&gt;But look at who decided what. The fraud system decided the score. The risk system decided the rating. The shop’s limit was checked by the service that owns limits. Our module matches the answers up by payment number, fills in the blanks as they arrive, and displays how far along we are. Everything we hold was decided somewhere else; nothing can be refused, and nothing new comes out of us.&lt;/p&gt;
&lt;p&gt;It becomes a candidate when we’re asked to rule on the answers rather than collect them: fraud beats an automatic approval, and once the fraud score and the limit check are both in, the payment is settled one way or the other. Now we hold state, apply a rule the business will argue about, and produce our own event saying verification is complete. That’s the moment I described in &lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;Dealing with Race Conditions&lt;/a&gt;, where a projection had quietly turned into a workflow.&lt;/p&gt;
&lt;p&gt;So they all fail for the same reason: events flow through the module, and the module decides nothing.&lt;/p&gt;
&lt;p&gt;As a rule of thumb: someone is waiting for an answer; the answer can be no, and something happens on its own while they wait.&lt;/p&gt;
&lt;h2 id=&quot;a-decent-pick&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#a-decent-pick&quot; aria-label=&quot;a decent pick permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;A decent pick&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;A customer wants to send back a pair of shoes.&lt;/p&gt;
&lt;p&gt;They ask for a return; we check whether it’s still within the return window and whether we take it back at all. If it is, we send them a label and wait. The parcel may arrive next week, or never. When it arrives, warehouse staff scan it, inspect the item, and decide whether the shoes are in the state the customer claimed. Then the return is granted, and finance pays out, or it’s refused, and we ship the shoes back.&lt;/p&gt;
&lt;p&gt;The events in a warehouse could look like that (pardon me if you’re a Warehousing expert; honestly, I’m not):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ReturnRequested
ReturnRefused
ReturnLabelSent
ParcelArrivedAtWarehouse
ItemInspected
ItemFoundWorn
ReturnGranted
ReturnAbandoned&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We decide two things here: whether to accept the return at all, which we can refuse when the customer asks on day 45 of a 30-day window. And whether the shoes came back in the state they described, which we can refuse after inspection. The courier and the scanner tell us where the parcel is.&lt;/p&gt;
&lt;p&gt;Then there’s &lt;code class=&quot;language-text&quot;&gt;ReturnAbandoned&lt;/code&gt;. It fires thirty days after the label was sent, when the parcel never showed up. Nobody triggers it manually. If we talk to warehouse managers, they will happily argue about whether thirty days is the right number. And hey, this is what a basic handling policy looks like.&lt;/p&gt;
&lt;p&gt;And if we get any of it wrong for a day, support fixes it by hand, and the customer waits an extra day for their money.&lt;/p&gt;
&lt;p&gt;The CRUD version is a returns table with a status column: requested, label_sent, received, granted. Each value overwrites the previous one, and so does updated_at.&lt;/p&gt;
&lt;p&gt;Six months later, finance wants to know whether shortening the window to 30 days made customers send things back sooner or just made us refuse more of them. The table holds current statuses and one timestamp per row, so it can’t answer either half. If our support team finds that some customers got refunded twice and asks whether the same parcel was inspected twice, the table shows just one “granted” and never records the first inspection at all.&lt;/p&gt;
&lt;p&gt;Returns also make the business case without hand-waving. With a CRUD version and a status column, we know how many returns are open. With Event Sourcing events, we know how many arrive after the window closed, how long parcels sit in the warehouse before inspection, how often an inspection overturns what the customer told us, and which products come back worn.&lt;/p&gt;
&lt;p&gt;Someone in operations asks all four of those within a month, and a status column has already erased the answers. That’s the &lt;a href=&quot;/en/never_lose_data_with_event_sourcing/&quot;&gt;never lose data&lt;/a&gt; argument, small enough to fit in a pilot.&lt;/p&gt;
&lt;p&gt;Returns aren’t the only feature like this; for instance, budget approval works the same way: someone submits a budget, the financial director approves or rejects it, and an approved one can no longer be changed. So does a reservation that we hold for a while and release when nobody confirms it.&lt;/p&gt;
&lt;p&gt;The waiting is the part we want in a pilot. The courier answers when it answers, and the thirty-day clock runs on its own. The read model showing open returns will be behind the events for a while, and the same courier callback will arrive twice sooner or later. We have to handle both of those rather than read about them.&lt;/p&gt;
&lt;p&gt;We also don’t want to learn everything at once. Inside one module we control the order in which our own events land, and we can read what we just wrote. Once returns starts talking to the finance module over a queue, we get out-of-order delivery, timestamps from machines whose clocks disagree, and the question of what to do when the payout confirmation arrives before we recorded that we asked for the payout. I wrote about handling that in &lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;Dealing with Race Conditions&lt;/a&gt;. It’s a fourth learning curve on top of the three we already have.&lt;/p&gt;
&lt;h2 id=&quot;dont-forget-to-audit-the-tooling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dont-forget-to-audit-the-tooling&quot; aria-label=&quot;dont forget to audit the tooling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Don’t forget to audit the tooling&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The pilot is also a chance to check the tooling. If we found a proper business concept that makes decisions, we should also check if we got strong consistency guarantees, for instance, ACID, optimistic concurrency. We can also check what options it gives for building read models, processing asynchronous subscriptions, etc. This will tell us a lot about how mature the tooling we selected is. And no, vibe-coded tools are not a good choice for critical infrastructure like an event store.&lt;/p&gt;
&lt;p&gt;When I wrote &lt;a href=&quot;/en/lets_build_event_store_in_one_hour/&quot;&gt;Let’s build event store in one hour!&lt;/a&gt;, I listed what an event store has to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;appending an event at the end of the stream,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;reading all events from the stream,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a guarantee of ordering within the stream,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;being able to read our writes,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;strong consistency, atomic writes and optimistic concurrency.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the second tier, good to have rather than mandatory:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;subscribing to notifications about newly appended events, best if push-based,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;global ordering,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;built-in projections,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;streams archiving.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s a growing number of vibe-coded libraries that look like event stores and skip parts of the first tier. Optimistic concurrency is the usual omission, since it’s the hardest to implement. That means: you’ll have to deal with that on your own. Without such features, you’ll be vulnerable to race conditions, data loss, and eventual inconsistencies.&lt;/p&gt;
&lt;p&gt;So it’s worth running these against our pilot:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;append to the same stream from two places at once and see whether one of them is rejected,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;kill the process while a projection is being updated, and check what state we come back to,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rebuild a projection from scratch,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;add a field to an event type that already has data stored,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;write a test for a single decision, and see how much setup it needs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last one is about developer experience rather than correctness, and it’s still worth measuring, because that’s what the team does all day.&lt;/p&gt;
&lt;p&gt;It’s also worth preferring an event store that runs on storage we already have. If we can put it on the Postgres our organisation already operates, nobody has to agree to run a new database for an experiment, and we skip the conversation with the platform team. A lot of pilots stop right there. It doesn’t get the library out of the checks above, though. Running on familiar storage says nothing about whether it handles concurrent appends.&lt;/p&gt;
&lt;h2 id=&quot;showcasing-the-pilot-outcome&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#showcasing-the-pilot-outcome&quot; aria-label=&quot;showcasing the pilot outcome permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Showcasing the pilot outcome&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If we argue and try to persuade others during a meeting to do Event Sourcing, it’s not easy. We don’t have proof that this can work; we just have rumours and strong belief. We may immediately put ourselves in a defensive position, as others may fear the risk of change. And it’s fair to be afraid.&lt;/p&gt;
&lt;p&gt;That’s why picking the scope is so critical. The pilot has to be small enough that we can do it slightly rogue. Not asking for a formal decision is reasonable when nobody is harmed by us spending a short time working on it.&lt;/p&gt;
&lt;p&gt;Rogue means not asking permission, and being prepared to ask for forgiveness. We should still have justification for it and authority to make such a decision (read more in &lt;a href=&quot;/en/business-wont-let-me-and-other-lies/&quot;&gt;Business Won’t Let Me and other lies we tell to ourselves&lt;/a&gt;). It shouldn’t mean hiding the work from our team or writing something only we can maintain, because that destroys the persuasion we were going for. We also shouldn’t hide from ownership of our decision.&lt;/p&gt;
&lt;p&gt;That’s why I recommend working on a feature &lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;with removability in mind&lt;/a&gt;. It helps if we fail, because then we at least minimise the consequences of a bad decision.&lt;/p&gt;
&lt;p&gt;If we fail, we drop our pilot; if we succeed, we can showcase what we did. Showing a returns process that has been running for two months, next to the chart of how many parcels never arrive, and mentioning that we’re using Event Sourcing, sparks much more proactive discussion than an imaginary scenario. The same inertia that blocks new ideas starts working for us once the code exists, because it’s hard to throw away something that already works.&lt;/p&gt;
&lt;p&gt;Once one feature works, the next steps get earned by evidence rather than enthusiasm. A second stream type when we find another process with real decisions. A separate read model store when the queries start to hurt. Cross-module messaging when two modules genuinely need to talk. Each of those should answer something we observed in production.&lt;/p&gt;
&lt;p&gt;None of this is specific to Event Sourcing. Any pattern new to the team deserves the same treatment: a real but forgiving feature, one learning curve at a time, an exit we’ve thought about, and something to show at the end. There’s nothing magical about it, we just forget the pragmatism when we’re excited.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Fractal Architecture, Cognitive Load, Vertical Slices and other terms that do(n't) fit your head]]></title><description><![CDATA[I wrote on LinkedIn that it seems that there are never enough articles on Vertical Slices. Each time I write a follow-up, there’s another…]]></description><link>https://event-driven.io/en/fractal-architecture-cognitive-load/</link><guid isPermaLink="false">https://event-driven.io/en/fractal-architecture-cognitive-load/</guid><content:encoded>&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/posts/oskardudycz_vertical-slices-their-ownership-and-external-activity-7492601542806609920-4Zkh?utm_source=share&amp;#x26;utm_medium=member_desktop&amp;#x26;rcm=ACoAAAN8Nx8B98JMAXELXW2GQXKrHtPuoimvwFc&quot;&gt;I wrote on LinkedIn&lt;/a&gt; that it seems that there are never enough articles on Vertical Slices. Each time I write a follow-up, there’s another follow-up needed. And guess what? After last week, I need another one!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7492601542806609920?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7492601542806609920%2C7492865005881679873%29&amp;#x26;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287492865005881679873%2Curn%3Ali%3Aactivity%3A7492601542806609920%29&quot;&gt;Adam asked me in the comments&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How do you see Fractal Architecture fitting into this approach? To me, it makes sense to place shared code in the smallest logical grouping of slices that use it, keeping it close to its usage while isolated from the rest of the application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And to give you a proper answer, I need to start by telling you about the Aussie guy called John Sweller.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5acdabaeed895b7a6011186c93c96040/38b44/image-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAQFA//EABUBAQEAAAAAAAAAAAAAAAAAAAED/9oADAMBAAIQAxAAAAGc0vrQbKRJ/8QAGxAAAgIDAQAAAAAAAAAAAAAAARECBAMiMjP/2gAIAQEAAQUCg2TrCs4sjGeK/l//xAAWEQADAAAAAAAAAAAAAAAAAAABECH/2gAIAQMBAT8BMX//xAAWEQEBAQAAAAAAAAAAAAAAAAABAhD/2gAIAQIBAT8BiBz/xAAaEAADAAMBAAAAAAAAAAAAAAAAARECEFGB/9oACAEBAAY/AqThU74ZaR//xAAaEAEBAQEAAwAAAAAAAAAAAAABEQAhEDFB/9oACAEBAAE/IRER49mKBZ6ZShOBAeU328L/2gAMAwEAAgADAAAAEOsv/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAEDAQE/EKpj/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERMf/aAAgBAgEBPxBG6Yf/xAAeEAEAAgIBBQAAAAAAAAAAAAABABEhMUFRYXGBwf/aAAgBAQABPxDC9QaGnWeMkIEXsjryczeaIp+sZOW675iXqYjfaz//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5acdabaeed895b7a6011186c93c96040/507e6/image-2.webp 200w,
/static/5acdabaeed895b7a6011186c93c96040/28a80/image-2.webp 400w,
/static/5acdabaeed895b7a6011186c93c96040/8d2ea/image-2.webp 800w,
/static/5acdabaeed895b7a6011186c93c96040/68fc1/image-2.webp 1200w,
/static/5acdabaeed895b7a6011186c93c96040/4b075/image-2.webp 1280w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5acdabaeed895b7a6011186c93c96040/37402/image-2.jpg 200w,
/static/5acdabaeed895b7a6011186c93c96040/4cda9/image-2.jpg 400w,
/static/5acdabaeed895b7a6011186c93c96040/c60e9/image-2.jpg 800w,
/static/5acdabaeed895b7a6011186c93c96040/6c738/image-2.jpg 1200w,
/static/5acdabaeed895b7a6011186c93c96040/38b44/image-2.jpg 1280w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5acdabaeed895b7a6011186c93c96040/c60e9/image-2.jpg&quot;
            alt=&quot;Schoolkids to say thanks for memory | The Australian&quot;
            title=&quot;Schoolkids to say thanks for memory | The Australian&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://onlinelibrary.wiley.com/doi/epdf/10.1207/s15516709cog1202_4&quot;&gt;In 1988, he coined an important term:&lt;/a&gt; &lt;strong&gt;&lt;a href=&quot;https://onlinelibrary.wiley.com/doi/epdf/10.1207/s15516709cog1202_4&quot;&gt;Cognitive Load&lt;/a&gt;&lt;/strong&gt;&lt;a href=&quot;https://onlinelibrary.wiley.com/doi/epdf/10.1207/s15516709cog1202_4&quot;&gt;.&lt;/a&gt; He said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Our working memory is only able to hold a small amount of information at any one time and that instructional methods should avoid overloading it in order to maximise learning”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When I was younger, I believed that I could multitask; I also bragged that I had such a good memory that when I started managing my calendar and taking notes, I got old. And yeah, it seems that I’m old now.&lt;/p&gt;
&lt;p&gt;When our industry evolved from the machines responsible for calculations and highly specialised machines and their operators, and became a big, valuable industry, we started to have issues with scaling. Not only technical scaling of distributed systems, but most importantly, scaling the work delivery.&lt;/p&gt;
&lt;p&gt;Our teams grew bigger, and more and more industries started to benefit from the automation that computers brought. Developers slowly stopped working in a narrow vertical structure with business folks, but became jacks of all trades and masters of none. Or actually, some had to become masters of the one: technology.&lt;/p&gt;
&lt;p&gt;The pendulum swung, and we landed in the &lt;del&gt;Helios&lt;/del&gt; Technology-centric world:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 772px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/07b2e4403c83a8b377ad14ab3589044c/37601/image-3.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBf/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAHSeFSaCjX/xAAZEAADAQEBAAAAAAAAAAAAAAAAAQIhEDH/2gAIAQEAAQUCdMT48Jwnz//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABQQAQAAAAAAAAAAAAAAAAAAACD/2gAIAQEABj8CX//EABoQAAMBAQEBAAAAAAAAAAAAAAABESExcaH/2gAIAQEAAT8hY58FzVS3lRr1vwx1ulPZ/9oADAMBAAIAAwAAABAsD//EABgRAAMBAQAAAAAAAAAAAAAAAAABQSFR/9oACAEDAQE/EFO0eH//xAAYEQACAwAAAAAAAAAAAAAAAAABERAhMf/aAAgBAgEBPxC2Xkf/xAAbEAEAAgMBAQAAAAAAAAAAAAABABEhMVFhQf/aAAgBAQABPxDS2t1x1l5kGk+kdwBhSoTgaGPYAGywA6PYxKVttZ//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/07b2e4403c83a8b377ad14ab3589044c/507e6/image-3.webp 200w,
/static/07b2e4403c83a8b377ad14ab3589044c/28a80/image-3.webp 400w,
/static/07b2e4403c83a8b377ad14ab3589044c/446d6/image-3.webp 772w&quot;
              sizes=&quot;(max-width: 772px) 100vw, 772px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/07b2e4403c83a8b377ad14ab3589044c/37402/image-3.jpg 200w,
/static/07b2e4403c83a8b377ad14ab3589044c/4cda9/image-3.jpg 400w,
/static/07b2e4403c83a8b377ad14ab3589044c/37601/image-3.jpg 772w&quot;
            sizes=&quot;(max-width: 772px) 100vw, 772px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/07b2e4403c83a8b377ad14ab3589044c/37601/image-3.jpg&quot;
            alt=&quot;Clean Coder Blog&quot;
            title=&quot;Clean Coder Blog&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;And yes, I know, in theory, the entities are “Enterprise Business Rules”, which means that business logic should be at the heart of our application, but effectively what you see, and where the emphasis is, are the layers. Even if you measure the surface of those layers, those technical layers are big enough that we can lose sight of what we are here for. And we’re here to deliver business value through software.&lt;/p&gt;
&lt;p&gt;Of course, for organisations it was easier to tell:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;hey, you don’t need to care what you do, just deliver this nicely looking UI,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;just take care of those distributed services integration, ensure they work, and you don’t need to care what they do,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hey, you’re the chosen one, smarty pants, right? Yeah, you’ll work on the domain.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It was justifiable to some degree, since all of that required specific technical skills that took time to master. It was also easier to just throw people who were supposed to be “good enough” to solve cases. Learning domains takes time. For me, it was usually about a year before I felt comfortable enough to understand the business. So people hoped that breaking down the work by technical aspects would cut the onboarding time.&lt;/p&gt;
&lt;p&gt;But well, it ended up as something explained by a similar diagram:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 397px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9ea6c9efb560df18cbc1142e08934cc5/ff5a8/image-4.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 118.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAYABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIBBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAe3gUwJCwf/EABcQAQEBAQAAAAAAAAAAAAAAAAABEDH/2gAIAQEAAQUC4mVFdTP/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAUEAEAAAAAAAAAAAAAAAAAAAAw/9oACAEBAAY/Ah//xAAbEAADAQEAAwAAAAAAAAAAAAAAARExQSGhsf/aAAgBAQABPyGao3nhEN8NdNDgTVfZT//aAAwDAQACAAMAAAAQ0AcA/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPxAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAHRAAAwACAgMAAAAAAAAAAAAAAAERITFRkUFxof/aAAgBAQABPxBwaaLyqaTq50PiJJJ1Xbgu6/R5LicGFZ2ZaJ9DiKLtH//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/9ea6c9efb560df18cbc1142e08934cc5/507e6/image-4.webp 200w,
/static/9ea6c9efb560df18cbc1142e08934cc5/ae302/image-4.webp 397w&quot;
              sizes=&quot;(max-width: 397px) 100vw, 397px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/9ea6c9efb560df18cbc1142e08934cc5/37402/image-4.jpg 200w,
/static/9ea6c9efb560df18cbc1142e08934cc5/ff5a8/image-4.jpg 397w&quot;
            sizes=&quot;(max-width: 397px) 100vw, 397px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/9ea6c9efb560df18cbc1142e08934cc5/ff5a8/image-4.jpg&quot;
            alt=&quot;The Scavengers&amp;#39; Guide to Atlantis | the Adventures of the Orange Monk&quot;
            title=&quot;The Scavengers&amp;#39; Guide to Atlantis | the Adventures of the Orange Monk&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Yup, collapsed like Atlantis,&lt;/p&gt;
&lt;p&gt;Why did this fail? I wrote about it in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/what_does_mr_bean_opening_the_car_have_to_do_with_programming/&quot;&gt;What does Mr Bean opening the car have to do with programming?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/onion_clean_code/&quot;&gt;What onion has to do with Clean Code?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short? It skyrocketed the cognitive load. Our architecture should be optimised to keep together things that change together. That way, we can work within a certain scope without jumping from one place to another. And that’s what working with layered architecture looks like.&lt;/p&gt;
&lt;p&gt;To add one web api endpoint, you need to change presentation, application, domain, and storage layers, each kept entirely separately just for the sake of subjective cleanliness. Also, if you had to understand what you have to do to complete a feature that touches multiple other features, you need to have a PhD.&lt;/p&gt;
&lt;p&gt;In general, the investigations looked like wanting to check the weather on your phone, and then realising 2h later that you just bought a skateboard after going through the path of reminiscences brought by doom scrolling on social media. And you still don’t know if it’s going to rain or not.&lt;/p&gt;
&lt;p&gt;So the pendulum swung again. We like to bounce back and forth; just like a pendulum, we can’t stop somewhere in the middle. Many people are now claiming that we don’t need any layers, that we should be vertically aligned. So we should cut our applications down to only the business features. We don’t need any grouping; what we care about is being autonomous and delivering features separately.&lt;/p&gt;
&lt;p&gt;The initial idea was nice, something like &lt;em&gt;“let’s get back to the roots and think differently, keeping the focus on business”&lt;/em&gt;. Yet, we got semantic diffusion of the original idea, and what was heard the most was: no layers, just vertical features.&lt;/p&gt;
&lt;p&gt;I explained that in detail in &lt;a href=&quot;/en/my-thoughts-on-vertical-slices-cqrs/&quot;&gt;My thoughts on Vertical Slices, CQRS, Semantic Diffusion and other fancy words&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why is it an issue?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Have you seen a huge backlog in Jira? Just a huge to-do list filled with tasks for some features.&lt;/p&gt;
&lt;p&gt;How easy is it to understand what actually has to be done?&lt;/p&gt;
&lt;p&gt;How do you manage the priority?&lt;/p&gt;
&lt;p&gt;How do you understand dependencies, and why are those features actually added?&lt;/p&gt;
&lt;p&gt;Typically, that’s where the Jirology starts, and that’s why we hate it. But the issue originates somewhere else.&lt;/p&gt;
&lt;p&gt;Cynically, or using &lt;a href=&quot;https://en.wikipedia.org/wiki/Occam%27s_razor&quot;&gt;Occam’s razor&lt;/a&gt;, the answer is simple. More or less how W. Edwards Deming framed it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A bad system will beat a good person every time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If our goal is the same: scaling the development process and its output, then no matter what we change internally, which architecture we choose, it’ll diffuse into what we optimise for.&lt;/p&gt;
&lt;p&gt;So, as always, breaking the business capability into a smaller set of features was how many architects and CEOs distributed the work and made it parallel. Faster rarely means better. Unless we’re in the race car industry.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Also, for many software engineers, feature-based delivery was a way to simplify their lives.&lt;/strong&gt; They could write a vague ADR, with just a few sentences describing the idea. It’s easy to write something that sounds good, but without enough detail, it’s hard to tell the implications and the exact delivery plan.&lt;/p&gt;
&lt;p&gt;Now, when you have the vague plan accepted, you can slowly, feature by feature, deliver lower-quality, simplistic solutions, backing this up with the claim that&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Well, it’s done according to the specification we agreed on. We don’t have enough time to go again through this process. We need to deliver the feature.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sounds familiar? For me, too familiar. I have seen numerous cases where requirements drift one after another, slice by slice. And teams backing their next change with the previous one.&lt;/p&gt;
&lt;p&gt;I’ve also seen numerous cases where teams felt really comfortable narrowly focusing on the exact features without even asking what the bigger picture is or why it was added. That led to, obviously, a maze, duplication of code and responsibility and lots of features that have had a hard time talking to each other.&lt;/p&gt;
&lt;p&gt;Ok, so should our pendulum swing again, and should we get back to what, not my uncle, was praising?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Again, maybe the answer is to finally stop somewhere in the middle and focus on the outcome instead of the output? So, the business outcome and the software product.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Indeed, we should invest in decreasing the cognitive load and making our software easier to fit in our heads. We already learned that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;technical split is not great,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;feature split is better but still far from great.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Some time ago, two people I respect a lot started talking about Fractal Architecture.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/1ZyR_tgGTp8?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/t3rSCpcJzm0?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I’m afraid to write about it, not to start counting where it becomes semantically diffused. But that’s important.&lt;/p&gt;
&lt;p&gt;Mark Seemann also wrote a great book, &lt;a href=&quot;https://www.goodreads.com/en/book/show/57345272-code-that-fits-in-your-head&quot;&gt;Code That Fits in Your Head&lt;/a&gt;. Check it out; it’s a simple book teaching on why code is important and design activity, and why we need to invest in fast feedback loops and simulations. Highly relevant nowadays. Much better than reading next-gen AI bros’ hot takes.&lt;/p&gt;
&lt;p&gt;Nevertheless, the idea is that, indeed, if we want our software to do what it does, to deliver business value, we should focus on the business. Yet, to avoid ending up with the overfocus and feature factory (as we discussed), we should note that we could compose it. With the idea that big things are composed of small things.&lt;/p&gt;
&lt;p&gt;Ok, enough philosophy; let’s get back to the original example from the previous article. We discussed the following e-commerce order features:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 orders
    📁 verifying-order
    📁 confirming-order
    📁 registering-order
    📁 pending-orders
    📁 order-storage // 👈 look ma, technical concept!&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, if we added a shopping cart, then it could look like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 shopping-carts
    📁 adding-product-item
    📁 removing-product-item
    📁 confirming-cart
    📁 clearing-cart
    📁 cart-storage // 👈 look ma, technical concept!&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We could group that into the e-commerce module&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 e-commerce
    📁 shopping-carts
        📁 adding-product-item
        📁 removing-product-item
        📁 confirming-cart
        📁 clearing-cart
        📁 cart-storage
    📁 orders
        📁 verifying-order
        📁 confirming-order
        📁 registering-order
        📁 pending-orders
        📁 order-storage&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we zip that into the e-commerce system, we’d see:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 e-commerce
    📁 shopping-carts
    📁 orders&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We could continue to elevate that into, e.g. the whole ERP system with other modules like marketing, sales, etc. Each of them would have its own sub-modules.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 erp
    📁 e-commerce
    📁 marketing
    📁 sales&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Looks familiar?&lt;/p&gt;
&lt;p&gt;Yes, the layout is the same as the order module. What’s more, this can also go down, so:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 orders
    📁 verifying-order
        📁 anti-fraud-detection
            (...)
        📁 high-value-custome-verification
        📁 external-order-verification
    📁 (...)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’s the same repeated pattern. Just like fractals, they’re chaotic in the sense that each fractal is different, just like each software product is different, but if we look carefully enough, then those chaotic patterns start to repeat themselves.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/51a614d42541979b9141258a98ed7559/image-5.gif&quot; alt=&quot;File:Self-Similarity-Zoom.gif - Wikimedia Commons&quot; title=&quot;File:Self-Similarity-Zoom.gif - Wikimedia Commons&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So why isn’t that a standard if it’s so great and simple?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Things get hard when you start thinking about cross-boundary splits and their dependencies.&lt;/p&gt;
&lt;p&gt;We already discussed with the features factory that no-boundaries is not a solution. Even if we remove them all and make all things separate, they won’t magically become autonomous. They’ll still have dependencies, just hidden, which is the worst type of coupling you can have.&lt;/p&gt;
&lt;p&gt;Let’s look again at this example&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OrderStorage&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &apos;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;order&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Dependencies&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CheckDriver&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DriverId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;DriverStatus&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;VerifyOrderDependencies&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    storage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Pick&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;OrderStorage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;getOrder&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;saveOrder&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    drivers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;       
        checkDriver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckDriver&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Feature&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; verifyOrderHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    orderStorage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      getOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      saveOrder
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    drivers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      checkDriver
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    checkContractor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    checkDriver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; VerifyOrderDependencies&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; VerifyOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; order &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; driver &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkDriver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;driverId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;saveOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;verifyOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; driver &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We declare explicitly what our feature needs.&lt;/p&gt;
&lt;p&gt;Why couldn’t we do the same for the whole module?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; verifyOrderHandler&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CheckDriver&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; from ‘verifying&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;order’&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Dependencies&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OrdersDependencies&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    database&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       connectionString&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    drivers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;       
        checkDriver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckDriver&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Feature&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; orders &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  props&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; OrdersDependencies&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; VerifyOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; db &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;drizzle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;props&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;database&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;connectionString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; storage &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ordersStroage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;verifyOrder&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;verifyOrderHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// DEPENDENCIES&lt;/span&gt;
        drivers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; deps&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;drivers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 👈 EXTERNAL&lt;/span&gt;
        storage &lt;span class=&quot;token comment&quot;&gt;// 👈 INTERNAL&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    command
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     verifyOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 👈 EXPOSES&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;See, we define:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dependencies&lt;/strong&gt;: what do we need from the outside world, e.g. where our data is stored (connection string) and how drivers are checked (from the outside world). Then we can feed that to child components using both external dependencies (e.g. drivers) and other internal components (e.g. storage).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Capabilities:&lt;/strong&gt; what do we expose to the outside world (e.g. order verification, which may be used by other modules, or a dedicated API component wrapping orders).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We could do the same at the higher level and the lower level. Just like Fractals, or like Hive (as nicely put by Julien Topçu &amp;#x26; Thomas Pierrain).&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/JZBsy6PsCRY?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Read also the article &lt;a href=&quot;https://www.planetgeek.ch/2024/09/19/onion-hexagonal-clean-or-fractal-architecture-all-of-them-and-more/&quot;&gt;Onion, Hexagonal, Clean or Fractal Architecture? All of them, and more!!&lt;/a&gt; where Urs Enzler explains how he mixes all of that together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And that’s also what the &lt;a href=&quot;https://c4model.com/&quot;&gt;C4 model&lt;/a&gt; is doing:&lt;/strong&gt; embracing different levels. Although it has a hard limit of 4, so it’s not a true fractal, which makes sense, since we should also be cautious about setting up paths that are too deep.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Of course, that doesn’t mean we need to go crazy and define all dependencies this way.&lt;/strong&gt; If those are calls to inner module methods or some shared code, we can call it explicitly. Then we’re coupling those components together, but if it’s explicit and helps us tame complexity and make things more explicit, then why not?&lt;/p&gt;
&lt;p&gt;Nevertheless, that’s why I don’t like jargon like “bounded context”, as it’s vague, but it’s presented as precise. I prefer to be “explicitly implicit” and just say that we’re composing the application from a component tree (or building a fractal).&lt;/p&gt;
&lt;p&gt;Whether it’s a system, component, module, or slice, we can tackle that the same way. Of course, each will differ in responsibility, scope, etc., but as long as we have clear dependencies expressed as: here’s what we need, and a public API as: here is what you can get from us. We can freely compose our relationship.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;All of that allows us to keep our cognitive load at a level where what we see fits into our heads, and focus on the business features, not technicalities.&lt;/strong&gt; This helps us fight the forces we discussed today that push us to optimise for output rather than outcome.&lt;/p&gt;
&lt;p&gt;Thoughts?&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Vertical slices, their ownership and external dependencies]]></title><description><![CDATA[A few questions about Vertical Slices come up again and again, and they’re good ones. If a slice cuts through all the layers, do we get a…]]></description><link>https://event-driven.io/en/vertical-slices-and-dependencies/</link><guid isPermaLink="false">https://event-driven.io/en/vertical-slices-and-dependencies/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0847f173098baf6627cc13377fe86013/92290/2026-08-10-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADYElEQVR42h2T2U9bVxCHb6qqavrULVFbobSqlKJGUUlUFtHSgN1Qk2A222ACeMHYYN96X7G5vvd6t/GKzBpchaYKQVC1L33qP/f11g+jM3N09M3Mb84IilKk1Trk5fkFNzd/US7vc3JyzqtXr3n79pparcV573cOD0+4uPiDs7Me19d/9t+8eXPF1dUNZ6e9vh10ThCcDpFqqUJ7v06pWEX0p0mmishqlQ2bkx8ePyTuXCHu0My1QSaepFzpkIjLVCot6vUumb0q/sAehUIHwbpiJ+D1EfMH8Gz5WVsX2d5JY7K4GPjwA4yDH9HY0HERtVNzmbEZZ9jxJQkGMiQiaRJRqe8vL2+jyHWEsM9HWBTZ1TIHAglC/ijBYIZn8xuMffE+sYkvOXIa+LeZ5Z+6hOu5HtEbQJZKKGmZ3J5Mp1JBTmVoVZsIPtcmQY8HKZFgNxrXsmbwesOsWh08uvsem8MDlJdGUcyT3Cghfguukwt4SadzKLsS+XSGmpqjrqocNdsIfg0YcrtQY1EyoSBqKo2iwdOav/D911i+/Zji4ijq/Bi90Dp/qyKNbAYpk2cvqekmKVSUHPuKSqfeQoh63ARdDpRwCCkSQo7HyMejqIkk4uyPeIY+QZkb5XXExuHWc879Jsp7Eo1qh0K2SEkpUVQK/fa7+w2EyLaHrfU10qIPSYNmk1p1wSBlKUvYvswv925jHvqKtuhAtujJzI5g+GkCve4plgUTv7q9ZHdlSnKJ06ZWodfhYNu2TnjTTmxnm91IjJ1ND26nk8knk9y7/S6L9+/Qsi2iLj1BnHzIzOgjBEHgnVu3+ufA559hmJpCTqYQAg47kS27pluESqFAo9YgHIpgMpnQ6X/m2fRTfIvT5OwLKF4bqvsFcZ+Hae1+zjiLZdnM0HcP+uAHg/cRxI0XeFet2qTdjI+Ps7a2Rq1a5eT4WNuOQ207Ljh7ea5tU5vu0TFqLk8+X+Dy8pJoNNoHD34z2AfevfMpQj6d4kADSKkkMzMzhMNhisUizWaTbrdLr9fj9OyMTrtNs9GgkM/T1uAHBweI2h/W6/UsWyxYrVaMRiNCScnidruZm59ndXUVnU7HyPAwExMTTGm62LWBmZYWGRkZZWxsDLMmxZbL1fcNBoMWm1lZWenD/o//A+mORWnU7xxOAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/0847f173098baf6627cc13377fe86013/507e6/2026-08-10-cover.webp 200w,
/static/0847f173098baf6627cc13377fe86013/28a80/2026-08-10-cover.webp 400w,
/static/0847f173098baf6627cc13377fe86013/8d2ea/2026-08-10-cover.webp 800w,
/static/0847f173098baf6627cc13377fe86013/446e0/2026-08-10-cover.webp 1056w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/0847f173098baf6627cc13377fe86013/36ca5/2026-08-10-cover.png 200w,
/static/0847f173098baf6627cc13377fe86013/a3397/2026-08-10-cover.png 400w,
/static/0847f173098baf6627cc13377fe86013/a331c/2026-08-10-cover.png 800w,
/static/0847f173098baf6627cc13377fe86013/92290/2026-08-10-cover.png 1056w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/0847f173098baf6627cc13377fe86013/a331c/2026-08-10-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A few questions about Vertical Slices come up again and again, and they’re good ones.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If a slice cuts through all the layers, do we get a table per slice?&lt;/li&gt;
&lt;li&gt;If I’ve split the application into seven areas, are those seven bounded contexts, or seven slices of one? And does the answer change what they’re allowed to know about each other?&lt;/li&gt;
&lt;li&gt;How can a “verify order” slice check what a “register order” slice wrote, if the two aren’t supposed to know about each other?&lt;/li&gt;
&lt;li&gt;Where does the fetching code live when one screen needs data owned by another module, and the dependency rules block every place we could put it?&lt;/li&gt;
&lt;li&gt;Should a UI component know about the API call a neighbouring component makes?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For me, these are all versions of one question: &lt;strong&gt;what does a slice do when it needs something from the outside world?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Calling every area of an application a bounded context sets the bar for separation as high as it can go, because contexts are genuinely meant to be autonomous. Once you’ve done that, connecting any two of them looks like a violation, and the other questions have no legal answer.&lt;/p&gt;
&lt;p&gt;The assumption doing the damage rarely gets stated because it feels too obvious: a slice ought to be self-contained, so needing something from elsewhere means the cut was wrong. I held it myself for a while.&lt;/p&gt;
&lt;p&gt;I’ve written before about &lt;a href=&quot;/en/how_to_slice_the_codebase_effectively/&quot;&gt;how to slice the codebase effectively&lt;/a&gt; and shown &lt;a href=&quot;/en/vertical_slices_in_practice/&quot;&gt;a worked module&lt;/a&gt;, and I listed “you can’t share code between slices” as &lt;a href=&quot;/en/my-thoughts-on-vertical-slices-cqrs/&quot;&gt;one of the myths that grew around the pattern&lt;/a&gt;. What I didn’t do was show the positive answer. Minimise isn’t zero, so what does the non-zero look like once you type it out?&lt;/p&gt;
&lt;p&gt;Vocabulary is where this gets tangled, so let me start there.&lt;/p&gt;
&lt;h2 id=&quot;slice-module-context&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#slice-module-context&quot; aria-label=&quot;slice module context permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Slice, module, context&lt;/h2&gt;
&lt;p&gt;Three words that get used interchangeably, and I think a lot of the pain comes from that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A vertical slice is one piece of functionality, cut through the whole application.&lt;/strong&gt; For me, a slice is more a function than an entity. &lt;em&gt;“Verify a transport order”&lt;/em&gt; is a slice. It has a way in, some business logic, and whatever it reads and writes. If you’re thinking of it as a thing with a lifecycle, you’re probably thinking of an entity, which is a different concept that lives within the slice’s reach rather than being the slice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A module is a logical grouping of slices.&lt;/strong&gt; Orders is a module. It holds registering an order, verifying it, confirming it, and listing what’s pending. The grouping is a judgement call, and the criterion I use is what changes together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A bounded context, in DDD terms, is a linguistic barrier.&lt;/strong&gt; It’s a set of functionality that the business uses the same vocabulary for, distinct from other contexts. In a given context, a word always means the same thing. Across contexts, the same word can mean something else entirely.&lt;/p&gt;
&lt;p&gt;A frontend and a backend aren’t two bounded contexts; they’re two deployment targets. You can have one deployment for multiple contexts, and a single bounded context with multiple deployments. If a frontend feature is part of the bounded context and has backing WebAPI, then the business should use the same words across both. Two product listings that differ in which fields they show aren’t two contexts either, since nothing about the vocabulary changes between them. Draw the line in either place, and you get the terminology without the boundary (or just a pure technical boundary), and then wonder why the &lt;em&gt;“contexts”&lt;/em&gt; need constant coordination.&lt;/p&gt;
&lt;p&gt;Seven features of one application are almost always slices, or at most modules, sitting inside a single context. That’s good news, because it means they were never obliged to be autonomous.&lt;/p&gt;
&lt;p&gt;I’ll be honest: I don’t love the term “bounded context”. It’s imprecise in the original books and even more misleading in practice, because most people encounter it third-hand and take it to mean &lt;em&gt;“a big folder we agreed to keep apart”&lt;/em&gt;. If the word causes arguments on your team, drop it and talk about which functionalities share a vocabulary. That usually works better.&lt;/p&gt;
&lt;p&gt;Labels aside, what I do in practice is start from the functionalities we have to deliver, group them logically into components, and check that grouping against the UX map. Technological splits (a layer for data access, a layer for services, one for the frontend and one for the backend) have always ended badly in my projects. Business grouping usually held up longer.&lt;/p&gt;
&lt;h2 id=&quot;slices-boundaries-in-practice&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#slices-boundaries-in-practice&quot; aria-label=&quot;slices boundaries in practice permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Slices boundaries in practice&lt;/h2&gt;
&lt;p&gt;Let’s use the transport order. It gets registered, then verified, then confirmed, then settled. Verification means checking that the contractor and the driver actually exist in the carrier’s own system, which is an external API we don’t control.&lt;/p&gt;
&lt;p&gt;It’s tempting to model this as one &lt;code class=&quot;language-text&quot;&gt;orders&lt;/code&gt; row with a &lt;code class=&quot;language-text&quot;&gt;status&lt;/code&gt; column that moves &lt;code class=&quot;language-text&quot;&gt;registered → verified → confirmed&lt;/code&gt;, updated through a generic endpoint. That’s how it usually starts, and the trouble begins there, because the endpoint that changes &lt;code class=&quot;language-text&quot;&gt;status&lt;/code&gt; also changes the pickup address and the contact phone. The system records that the order is verified. It doesn’t record that anybody verified it.&lt;/p&gt;
&lt;p&gt;Greg Young made this argument better than I will in &lt;a href=&quot;https://cqrs.wordpress.com/documents/task-based-ui/&quot;&gt;Task-Based UI&lt;/a&gt;: when the client posts data-centric structures back and forth, the domain has no verbs, and the user’s intent is lost on the way in. His point is that the client should tell the server to &lt;em&gt;do something&lt;/em&gt;, so that the intent becomes the exact task for the backend expressed by the message we sent (rather than being inferred from a combination of fields mixed with the current state).&lt;/p&gt;
&lt;p&gt;Naming the operation is also what gives you something to slice along. If every operation is “update the order”, you have one feature and nothing to divide. Once you have &lt;code class=&quot;language-text&quot;&gt;VerifyOrder&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;ConfirmOrder&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;RejectOrder&lt;/code&gt;, you have folders, and each folder is named the way the business names the operation. This holds for plain CRUD systems too. The name is the value, and the underlying implementation can be a single &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So we get:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 orders
    📁 verifying-order
    📁 confirming-order
    📁 registering-order
    📁 pending-orders&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which leaves the question I started with. Verification needs the carrier’s system, and confirming needs to know what verification established. Where does that go?&lt;/p&gt;
&lt;p&gt;The shape I use is a handler taking two arguments: its dependencies, then the message.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// orders/verifying-order/verifyOrder.ts&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ContractorStatus&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Active&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Unknown&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Suspended&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DriverStatus&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Licensed&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Unknown&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Expired&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Dependencies&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CheckContractor&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ContractorId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ContractorStatus&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CheckDriver&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DriverId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;DriverStatus&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Handler&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; verifyOrderHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    getOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    saveOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    checkContractor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    checkDriver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    getOrder&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GetOrder&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    saveOrder&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SaveOrder&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    checkContractor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckContractor&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    checkDriver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckDriver&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; VerifyOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; order &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;contractor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; driver&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;checkContractor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contractorId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;checkDriver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;driverId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;saveOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;verifyOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contractor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; driver &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first parameter is dependencies, and I think about it the same way I think about &lt;a href=&quot;https://react.dev/learn/passing-props-to-a-component&quot;&gt;React props&lt;/a&gt;. The component declares what it expects; whoever renders it decides what to pass. Same here: the handler declares what it expects, and it has no opinion about where those functions come from: the same module, a different module, an HTTP client, or a stub.&lt;/p&gt;
&lt;p&gt;The business logic underneath takes everything as data and returns a decision:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// orders/verifying-order/verifyOrder.ts&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Business logic&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; verifyOrder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; VerifyOrder &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    contractor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ContractorStatus&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    driver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DriverStatus&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  order&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Order&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Order &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Registered&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Cannot verify an order in &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; state&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contractor &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Active&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Contractor is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contractor&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;driver &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Licensed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Driver licence is &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;driver&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Verified&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    verifiedBy&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;verifiedBy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    verifiedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nothing here is fetched, so testing it means passing values in and checking what comes out. No mocks, no container, no database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There’s no &lt;code class=&quot;language-text&quot;&gt;ITransportManagementSystem&lt;/code&gt; interface here with fifteen methods.&lt;/strong&gt; Pragmatically, you can pull the whole thing in, and plenty of codebases do, but I’ve come to prefer narrowing it: two functions, three cases each, defined in this folder, in this slice’s language.&lt;/p&gt;
&lt;p&gt;The carrier’s system almost certainly has a richer model of a contractor than the three cases, including credit terms, insurance validity, certificate expiry dates, and territorial permissions. Verification only needs to know whether this order can proceed. Declaring the narrow type keeps the slice in its own vocabulary rather than importing someone else’s, which is the bounded-context idea applied on a much smaller scale.&lt;/p&gt;
&lt;h2 id=&quot;duck-typing-and-dependencies&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#duck-typing-and-dependencies&quot; aria-label=&quot;duck typing and dependencies permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Duck typing and dependencies&lt;/h2&gt;
&lt;p&gt;Nothing in the codebase declares that it implements &lt;code class=&quot;language-text&quot;&gt;CheckContractor&lt;/code&gt;. There’s no &lt;code class=&quot;language-text&quot;&gt;implements&lt;/code&gt; clause anywhere. Any function of a compatible shape satisfies it. That means the type can live with the consumer, the slice that needs it, rather than with whoever ends up providing it.&lt;/p&gt;
&lt;p&gt;So you can write the need, the handler, and the tests before anyone has decided who will serve it. Whether &lt;code class=&quot;language-text&quot;&gt;checkContractor&lt;/code&gt; becomes an HTTP call, a query against a table we replicate nightly, or a function returning &lt;code class=&quot;language-text&quot;&gt;&apos;Active&apos;&lt;/code&gt; for the first three weeks stays open. When an external shape doesn’t match, you fit it or remap it at the point of supply, and neither side changes.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@raafvargas/dependency-injection-in-go-35293ef7b6&quot;&gt;Golang bets its whole dependency story on this&lt;/a&gt;. Interfaces are satisfied implicitly, and the convention is that the consumer declares the interface it needs, sized to its use. Hence the proverb about the bigger interface being the weaker abstraction. &lt;code class=&quot;language-text&quot;&gt;io.Reader&lt;/code&gt; is one method, and half the standard library composes through it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/en/structural_typing_in_type_script/&quot;&gt;Structural typing&lt;/a&gt; gives us that in TypeScript, and I think it’s underused by people arriving from C# and Java, where the habit is to define a nominal interface and hand it around.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So where do the dependencies come from?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Somewhere near the entry point, you create the real things once, build the small functions the handlers asked for, and pass them in:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// apps/api/composition.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; db &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;drizzle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DATABASE_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; carrier &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;carrierApiClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CARRIER_API_URL&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; contractorStatus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;client&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CarrierApiClient&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckContractor &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; contractor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getContractor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;contractor&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Unknown&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; contractor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;suspendedAt &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Suspended&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Active&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; orders &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  getOrder&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getOrderFrom&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  saveOrder&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;saveOrderTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  checkContractor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;contractorStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;carrier&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  checkDriver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;driverStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;carrier&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That mapping function is where two vocabularies meet, and I like having exactly one place where that happens. Everything the carrier knows about a contractor collapses into three cases, in a file you can read in ten seconds.&lt;/p&gt;
&lt;p&gt;The routes are then thin:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// apps/api/routes.ts&lt;/span&gt;
app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/orders/:id/verification&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;verifyOrderHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orders&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    verifiedBy&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    now&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;204&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s the mechanism: partial application, done by hand, in a file whose job is to know about everything so that nothing else has to. No container, no registration, no lifetime scopes. In a monorepo, this file lives in &lt;code class=&quot;language-text&quot;&gt;apps/&lt;/code&gt;, and &lt;code class=&quot;language-text&quot;&gt;packages/&lt;/code&gt; holds slices that declare but never resolve. If you’re using Nx, this is the composition point its boundary rules exist to protect. In other environments, where Dependency Injection Containers are out of the box, you can still use the same way; in .NET, you can use &lt;a href=&quot;https://jeremybytes.blogspot.com/2024/02/method-injection-in-aspnet-core-api.html&quot;&gt;the same pattern by injecting dependencies explicitly even to methods&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The tests use the same shape:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; contractor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ContractorStatus&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckContractor &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;refuses to verify an order for a suspended contractor&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; store &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;inMemoryOrderStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;registeredOrder&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;verifyOrderHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;store&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;carrierStubs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; checkContractor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;contractor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Suspended&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      verifyCommand&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rejects&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toThrow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Contractor is Suspended&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There’s no mocking framework here, because there’s nothing to mock. You can pass an in-memory implementation, a stub, or a real client pointed at a sandbox. The handler can’t tell the difference.&lt;/p&gt;
&lt;h2 id=&quot;everything-outside-the-slice-is-external&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#everything-outside-the-slice-is-external&quot; aria-label=&quot;everything outside the slice is external permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Everything outside the slice is external&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;From inside a slice, there’s one category of thing: external.&lt;/strong&gt; Another slice next door, the parent module, a different module, a third-party API, the database. All the same. The slice states a function type and doesn’t ask where the implementation comes from.&lt;/p&gt;
&lt;p&gt;That keeps the slice movable. Because it defines its own dependencies, changing the logical grouping later is a matter of what you inject, not a rewrite. If verification turns out to belong in its own module, or gets extracted into a service, the handler stays as it is. Testing in isolation comes out of the same property, as the test above shows, and I get it as a side effect rather than designing for it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;None of this is about hiding the coupling.&lt;/strong&gt; For me, independence isn’t a value in itself. I’d rather know the connections I need to have and be able to look at the code to see what it depends on and what it does. Hidden dependencies are still there, only harder to find. What I’m optimising for is cohesion, and explicit dependencies serve that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What about the module’s public API?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you’ve read my &lt;a href=&quot;/en/my-thoughts-on-vertical-slices-cqrs/&quot;&gt;Architecture Weekly piece on VSA&lt;/a&gt;, you’ll have seen me recommend an &lt;code class=&quot;language-text&quot;&gt;api.ts&lt;/code&gt; per module, exposing what’s public and hiding the rest. That may seem to contradict what I’ve just described.&lt;/p&gt;
&lt;p&gt;They point in opposite directions across the same boundary. &lt;code class=&quot;language-text&quot;&gt;api.ts&lt;/code&gt; is what a module &lt;strong&gt;offers&lt;/strong&gt;: the surface it’s willing to support and that the module owns. &lt;code class=&quot;language-text&quot;&gt;CheckContractor&lt;/code&gt; is what a slice &lt;strong&gt;asks for&lt;/strong&gt;: a need, owned by the consumer, expressed in the consumer’s terms.&lt;/p&gt;
&lt;p&gt;You want both, because they protect against different things. Without the module API, everything is reachable, and any internal change can break a caller you didn’t know you had. Without consumer-declared needs, the consumer is coupled to the shape of whatever the provider decided to expose, including the parts it never calls.&lt;/p&gt;
&lt;p&gt;The composition root is where the two meet. It takes what the module offers and adapts it to what the slice asked for. That adapter is a few lines, it lives in one place, and it’s the only code that knows both vocabularies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cycles stop being a problem.&lt;/strong&gt; Orders needs contractor standing. Pricing needs order history to work out volume discounts for the same contractor. That’s a genuine mutual need, and if both modules import each other, you have a cycle. The compiler may tolerate it. Your boundary rules probably won’t. This is the situation where the tooling forbids two modules at the same layer from referencing each other, and it’s easy to conclude from that the design must be wrong.&lt;/p&gt;
&lt;p&gt;The usual escape is to extract the shared parts into a common module. That works twice; then the common module becomes the place everything ambiguous lands, and changing it means changing everything.&lt;/p&gt;
&lt;p&gt;But there’s no cycle if neither module imports the other. Orders declares &lt;code class=&quot;language-text&quot;&gt;CheckContractor&lt;/code&gt;. Pricing declares its own:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// pricing/volume-discounts/orderHistory.ts&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CompletedOrder&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Readonly&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  completedOn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  netValue&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Money&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GetCompletedOrders&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  contractorId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ContractorId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  since&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;CompletedOrder&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Two fields, because that’s what a volume discount calculation uses. The composition root supplies a function that queries orders and maps the result down to that shape.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most module-level cycles I’ve run into were a shared concept whose owner hadn’t been decided yet.&lt;/strong&gt; Declaring narrow needs lets you defer that decision rather than resolve it early and wrong. If it later turns out that contractor standing genuinely belongs in one place with a single definition, you’ll know more by then, and moving it is cheap because only the adapters point to it.&lt;/p&gt;
&lt;p&gt;If your dependency rules forbid the arrangement your domain wants, it’s worth checking whether they’re describing your design or your import graph. Rules that block a module from reaching another module are useful. Rules that block it because two files sit at the same “layer” are enforcing a layering you may have already outgrown.&lt;/p&gt;
&lt;h2 id=&quot;two-entry-points-one-feature&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#two-entry-points-one-feature&quot; aria-label=&quot;two entry points one feature permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Two entry points, one feature&lt;/h2&gt;
&lt;p&gt;What if the same operation can be triggered two ways?&lt;/p&gt;
&lt;p&gt;An order gets confirmed by a dispatcher through the UI. It also gets confirmed automatically when the carrier’s system reports the assignment accepted. Same rule, different trigger, different surrounding logic.&lt;/p&gt;
&lt;p&gt;Both files go in &lt;code class=&quot;language-text&quot;&gt;confirming-order/&lt;/code&gt;, side by side. I showed this pattern in &lt;a href=&quot;/en/vertical_slices_in_practice/&quot;&gt;Vertical Slices in practice&lt;/a&gt; using an API endpoint and an external booking event that share a single command handler. With dependencies declared per handler, the event-triggered one looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// orders/confirming-order/confirmOrderOnAssignmentAccepted.ts&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; confirmOrderOnAssignmentAccepted &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; saveOrder &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getOrder&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GetOrder&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; saveOrder&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SaveOrder &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AssignmentAccepted&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; order &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;saveOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;confirmOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        confirmedBy&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SystemUser&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        now&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;acceptedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      order&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The two handlers don’t have the same dependencies.&lt;/strong&gt; This one has no carrier check at all, since the event came from the carrier and we already have the answer. The dispatcher-triggered version loads more, because it starts with less. The business logic is shared; the application logic isn’t, and each entry point declares what it actually uses.&lt;/p&gt;
&lt;p&gt;The same applies to what you reuse across slices. I’d reuse a policy or a calculator, say a pure function like &lt;code class=&quot;language-text&quot;&gt;settlementFor(order, tariff)&lt;/code&gt; used by both confirmation and settlement, before I’d reuse a whole handler. The pure function is a function of its arguments and cheap to share. Handlers differ in what they load, check and record, and that’s the part that tends to change.&lt;/p&gt;
&lt;h2 id=&quot;vertical-slices-and-database&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#vertical-slices-and-database&quot; aria-label=&quot;vertical slices and database permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Vertical Slices and Database&lt;/h2&gt;
&lt;p&gt;Back to the table per slice. Three separate things get conflated in that question.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Business logic goes per entity or aggregate.&lt;/strong&gt; The rules about what states an order can be in and which transitions are legal belong to the order. One place, and every slice that decides about an order goes through it. Slices don’t each get a private notion of what an order is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read models go per query.&lt;/strong&gt; This is where a table per feature is right. The dispatcher’s pending-orders board needs order number, contractor, pickup window and verification state, sorted by pickup time. Build a table that answers that. The settlement report needs something else and gets its own. Resist making a single query serve five screens by expanding columns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Database schemas go per module.&lt;/strong&gt; One for orders, one for pricing. A slice is a feature, not a persistence boundary. A schema per slice gives you migrations that correspond to nothing, and joins across five schemas to draw a single screen.&lt;/p&gt;
&lt;p&gt;You can do all of this with one status column and an ORM. If you want the four distinct operations that currently collapse into &lt;code class=&quot;language-text&quot;&gt;status = &apos;cancelled&apos;&lt;/code&gt; to stay distinguishable, appending them as facts is what &lt;a href=&quot;/en/vertical_slices_in_practice/&quot;&gt;Event Sourcing&lt;/a&gt; offers, and it composes well with this. Nothing above depends on it.&lt;/p&gt;
&lt;h2 id=&quot;vertical-slices-and-frontend&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#vertical-slices-and-frontend&quot; aria-label=&quot;vertical slices and frontend permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Vertical Slices and Frontend&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Don’t force a 1:1 mapping between the UI and the backend.&lt;/strong&gt; One screen is routinely composed of data gathered from several modules, and one user action can trigger operations in several. That mismatch is normal. GraphQL exists partly as an attempt to solve it, whatever you make of that as a solution.&lt;/p&gt;
&lt;p&gt;A dispatcher board showing pending orders, contractor status and a confirm button touches three slices across two modules. Two reasonable arrangements:&lt;/p&gt;
&lt;p&gt;Compose at the page. The page fetches from several endpoints and arranges the results, holding no business logic of its own. Components receive what they need as props and know nothing about where they came from, for the same reason that handlers take dependencies as arguments. A component that takes props is easy to move and easy to feed with a different implementation.&lt;/p&gt;
&lt;p&gt;Or write a backend-for-frontend. If the screen needs it in one request, make a slice whose job is to serve that screen, depending on the others and stitching them together. It’s still a slice; it’s named after a screen because that’s honestly what it is.&lt;/p&gt;
&lt;p&gt;Which one depends on how much the round-trip costs you and how stable the screen is. Both beat growing one endpoint until it serves every screen you have.&lt;/p&gt;
&lt;p&gt;The backend usually knows which operations are currently available for a given order, and the frontend is often re-deriving that from status fields. Returning the available actions with the data keeps that decision in one place. That’s the part of HATEOAS I find useful, without the rest of the ceremony.&lt;/p&gt;
&lt;p&gt;Splitting the frontend by kind of thing tends to look suspicious to me. Two listings that differ only in which fields they show are usually one feature with a parameter. Splitting by market or country is more often real, because it genuinely is a different application with different rules. It’s hard to judge from a description; what I’d do is map the functionalities first and see which ones actually change together.&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h2&gt;
&lt;p&gt;The question I started with was what a slice does when it needs something it doesn’t own. My answer is that it declares the need and leaves it to something else to decide where it comes from.&lt;/p&gt;
&lt;p&gt;Which comes down to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Everything outside the slice is external, whether it’s the next folder or another system.&lt;/li&gt;
&lt;li&gt;Declare narrow function types where they’re used, in your own vocabulary, rather than importing a wide interface.&lt;/li&gt;
&lt;li&gt;Compose by passing functions in, in one file, near the entry point.&lt;/li&gt;
&lt;li&gt;A module’s public API and a slice’s declared need are different directions of the same boundary, and the composition root adapts between them.&lt;/li&gt;
&lt;li&gt;Reuse policies and calculators; let handlers duplicate.&lt;/li&gt;
&lt;li&gt;Business logic per entity, read model per query, schema per module.&lt;/li&gt;
&lt;li&gt;Don’t expect the frontend to mirror the backend.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this needs a framework, a container or a particular database. It’s more about a convention applied consistently.&lt;/p&gt;
&lt;p&gt;And your first grouping will still be wrong somewhere. Mine usually is. That’s fine, as long as being wrong stays cheap, which is the argument for &lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;removability over maintainability&lt;/a&gt; and for keeping the couplings visible rather than tucked away. A slice that states its dependencies is one you can move.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And the cherry on top: this approach helps LLM agents too.&lt;/strong&gt; Everything a feature needs sits in one folder, with a handful of function types crossing the boundary. To change how verification works, you open &lt;code class=&quot;language-text&quot;&gt;verifying-order&lt;/code&gt;. The command, the rules, both entry points and the tests are in it.&lt;/p&gt;
&lt;p&gt;That was always the argument for grouping changes together: it reduces how much a person has to hold in their head. The same property determines how much can fit in a context window and how much of a codebase an agent has to read before it can safely change one behaviour. A layered structure that needs five folders touched for one feature costs an agent the same way it costs us, faster.&lt;/p&gt;
&lt;p&gt;So it’s an old argument that happens to have got more valuable.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;If you’re dealing with such issues, I’m happy to help you through consulting, &lt;a href=&quot;/en/training&quot;&gt;training&lt;/a&gt; or mentoring. &lt;a href=&quot;mailto:oskar@event-driven.io&quot;&gt;Contact me&lt;/a&gt; and we’ll find a way to unblock you!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Fixing bugs in Event Sourcing is hard, for real?]]></title><description><![CDATA[Every system ends up with bad data. Some of it comes from integrations, some from users doing things nobody predicted, and a good part of it…]]></description><link>https://event-driven.io/en/fixing-bugs-in-event-sourcing-is-hard/</link><guid isPermaLink="false">https://event-driven.io/en/fixing-bugs-in-event-sourcing-is-hard/</guid><content:encoded>&lt;p&gt;Every system ends up with bad data. Some of it comes from integrations, some from users doing things nobody predicted, and a good part of it comes from us, because we shipped a change that looked fine in review.&lt;/p&gt;
&lt;p&gt;This is about what we can do on Tuesday morning, when someone from support writes: &lt;em&gt;“the price on this reservation looks wrong”&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-bug&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-bug&quot; aria-label=&quot;the bug permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The bug&lt;/h2&gt;
&lt;p&gt;Imagine we’re working on a hotel reservations system. Pricing there is fiddly: rate plans get revised, extras are added after booking, and the city sets the tourist tax on its own schedule.&lt;/p&gt;
&lt;p&gt;Let’s say that the tourist tax used to be a single value in configuration, 4.50 per person per night, multiplied by nights and guests. That was enough for years, because the rate never moved.&lt;/p&gt;
&lt;p&gt;Then the city announced an increase to 7.00 from 1 April. It appeared that one value can’t express that: a stay from 30 March to 2 April has nights on both sides of the boundary. So we added rates with validity periods and a lookup to resolve them, and shipped it on 12 March.&lt;/p&gt;
&lt;p&gt;The lookup resolves the rate once per reservation, from the check-out date, and applies it to every night. For a stay inside a single period that gives the right answer, which is every case the tests covered. For a stay crossing 1 April, it charges 7.00 for the March nights too. Crazy, but well, that’s how life is sometimes.&lt;/p&gt;
&lt;p&gt;A rate change has people checking numbers they’d normally trust, and on 19 March a front office manager reported that the projected tax for early-April arrivals doesn’t match what she gets counting by hand. We confirmed it and deployed a fix at 16:02 the same day.&lt;/p&gt;
&lt;p&gt;Then the actual work starts. Which reservations are wrong, by how much, and what do we do about them?&lt;/p&gt;
&lt;h2 id=&quot;what-we-have-with-state&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-we-have-with-state&quot; aria-label=&quot;what we have with state permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What we have, with state&lt;/h2&gt;
&lt;p&gt;Take the reservation support asked about. Seven nights, two guests, 27 March to 3 April, booked on 14 March, breakfast added two days later. Here’s the row:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;| id    | check_in   | check_out  | rate_plan_id | total_amount | updated_at       |
|-------|------------|------------|--------------|--------------|------------------|
| 8f21  | 2026-03-27 | 2026-04-03 | RP-STD-2026  | 1358.00      | 2026-03-16 14:02 |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The total is wrong. What should it be? The row doesn’t say how the number was produced: no breakdown of rooms, extras and tax, no record of which nights got which tax rate, and no version on &lt;code class=&quot;language-text&quot;&gt;rate_plan_id&lt;/code&gt;, so we can’t tell which prices were in force on 14 March. &lt;code class=&quot;language-text&quot;&gt;updated_at&lt;/code&gt; says 16 March, which is the breakfast, not the booking.&lt;/p&gt;
&lt;p&gt;So we recompute and write the migration:&lt;/p&gt;
&lt;p&gt;It matches 1,240 rows. We run it at 17:30 on 19 March.&lt;/p&gt;
&lt;h2 id=&quot;what-that-migration-does&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-that-migration-does&quot; aria-label=&quot;what that migration does permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What that migration does&lt;/h2&gt;
&lt;p&gt;Around 900 of those rows genuinely came from the broken build. The other 340 were booked before 12 March, had correct totals, and were caught because someone changed a room or added a note during the window. &lt;code class=&quot;language-text&quot;&gt;updated_at&lt;/code&gt; moves for all of that, and there’s nothing in the row that separates “this changed” from “this changed because of the bug”. We recalculate them along with the rest.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;recalculate_total&lt;/code&gt; reads the rate plan as it stands today, and the plans were revised on 15 March for the season. RP-STD-2026 went from 180.00 a night to 205.00. For reservation 8f21 that means:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;|         | before      | after migration   | correct  |
|---------|-------------|-------------------|----------|
| rooms   | 1260.00     | 1435.00           | 1260.00  |
| extras  | 168.00      | 168.00            | 168.00   |
| cityTax | 98.00       | 73.00             | 73.00    |
| total   | 1526.00     | 1676.00           | 1501.00  |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The migration fixed the tax and repriced the rooms. It did that to every reservation booked before 15 March, roughly 400 of them, and each guest gets a confirmation with a total higher than the one they agreed to.&lt;/p&gt;
&lt;h2 id=&quot;and-then-migration-number-two&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#and-then-migration-number-two&quot; aria-label=&quot;and then migration number two permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;And then migration number two&lt;/h2&gt;
&lt;p&gt;Complaints reach us around 2 April, and we work out what happened. Now we need to put the room prices back. To do that, we need two things per reservation: the date it was booked, so we know which rate plan version applied, and what the total was before we touched it.&lt;/p&gt;
&lt;p&gt;We have neither. &lt;code class=&quot;language-text&quot;&gt;updated_at&lt;/code&gt; reads &lt;code class=&quot;language-text&quot;&gt;2026-03-19 17:30&lt;/code&gt; for all 1,240 rows we updated, so it no longer distinguishes the ones we broke from the ones we fixed from the ones we shouldn’t have touched. &lt;code class=&quot;language-text&quot;&gt;total_amount&lt;/code&gt; holds the value our migration wrote. The booking date was never stored separately, and the only thing that used to approximate it was &lt;code class=&quot;language-text&quot;&gt;updated_at&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So migration two is written against data that migration one produced. We reconstruct booking dates from confirmation emails where we still have them, guess at the rest, and pick a &lt;code class=&quot;language-text&quot;&gt;WHERE&lt;/code&gt; clause that we hope covers the right subset. If we get that wrong, the input to migration three is the output of migration two. I’ve been on both ends of this, and it doesn’t get more pleasant.&lt;/p&gt;
&lt;p&gt;We can prepare for this in a state-based system, of course. Keep the breakdown in a JSON column, store the rate plan version on the reservation, add a &lt;code class=&quot;language-text&quot;&gt;price_history&lt;/code&gt; table, add a &lt;code class=&quot;language-text&quot;&gt;corrected_by&lt;/code&gt; marker. I’ve done all of those. The catch is that we add each one after the incident that taught us to, on the table where that incident happened, and today’s incident is on a different table. What we’re building at that point is a partial event log, decided per column, under time pressure.&lt;/p&gt;
&lt;h2 id=&quot;the-same-bug-event-sourced&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-same-bug-event-sourced&quot; aria-label=&quot;the same bug event sourced permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The same bug, event-sourced&lt;/h2&gt;
&lt;p&gt;Here’s the stream for the same reservation:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;| when         | event                        | data                                                       |
|--------------|------------------------------|------------------------------------------------------------|
| 14 Mar 09:40 | `ReservationInitiated`       | 27 Mar – 3 Apr, DBL, 2 guests                              |
| 14 Mar 09:41 | `RatePlanApplied`            | RP-STD-2026 v4, 180.00 / night                             |
| 14 Mar 09:41 | `ReservationPriceCalculated` | rooms 1260.00, cityTax 98.00, total 1358.00                |
| 14 Mar 09:41 | `ReservationConfirmed`       |                                                            |
| 16 Mar 14:02 | `ExtraAdded`                 | breakfast, 2 guests, 168.00                                |
| 16 Mar 14:02 | `ReservationPriceCalculated` | rooms 1260.00, extras 168.00, cityTax 98.00, total 1526.00 |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The corrupted value is cityTax, and it’s sitting there in plain sight. With two guests, a March night costs 9.00 in tax and an April night 14.00, so five March nights and two April nights come to 45.00 + 28.00 = 73.00. The event says 98.00, which is all seven nights at 14.00. Everything else in that event is right, and RatePlanApplied still says the booking was made at 180.00 a night under version 4, so we can calculate the correct total without reconstructing anything:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;| What    | recorded | correct |
|---------|----------|---------|
| cityTax | 98.00    | 73.00   |
| total   | 1526.00  | 1501.00 |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s the difference from the table. The migration above went wrong because the inputs to the original calculation were gone, and here they aren’t. The 14 March event still says what it said on 14 March, whatever was appended later.&lt;/p&gt;
&lt;h2 id=&quot;knowing-which-events-to-look-at&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#knowing-which-events-to-look-at&quot; aria-label=&quot;knowing which events to look at permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Knowing which events to look at&lt;/h2&gt;
&lt;p&gt;The other half is metadata. Here’s the full event:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ReservationPriceCalculated&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;reservationId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;8f21&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;nights&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;date&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-03-27&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;room&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180.00&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;cityTax&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14.00&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;date&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-03-28&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;room&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180.00&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;cityTax&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14.00&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;date&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-04-02&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;room&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;180.00&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;cityTax&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;14.00&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;cityTax&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;98.00&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;total&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1358.00&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-03-14T09:41:12Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;correlationId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;b31e...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;causationId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;a07c...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;userId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;booking-engine&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;buildSha&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;a4f9c2e&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;schemaVersion&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There’s one interesting metadata: &lt;code class=&quot;language-text&quot;&gt;buildSha&lt;/code&gt;. What is it? It’s the commit the service was running when it appended the event. It’s an environment variable and a few lines in whatever builds our metadata. On 19 March, it lets us query by the thing we actually care about:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; stream_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metadata
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; events
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ReservationPriceCalculated&apos;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; metadata&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;buildSha&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;a4f9c2e&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That returns 900 events, and they’re the 900 the broken binary produced. The 340 reservations that were merely touched during the same week don’t appear, because their price events came from a different commit.&lt;/p&gt;
&lt;p&gt;The same metadata helps with the diagnosis. &lt;code class=&quot;language-text&quot;&gt;correlationId&lt;/code&gt; groups everything that came from one user action, and &lt;code class=&quot;language-text&quot;&gt;causationId&lt;/code&gt; chains back to the command that triggered this event. When the bad value comes out of a handler three hops from the request, that chain is how we find which request it was.&lt;/p&gt;
&lt;p&gt;Without the SHA, we’re still ahead, because the position and timestamp of an appended event don’t move once it’s written. Still, I recommend to add the sha.&lt;/p&gt;
&lt;h2 id=&quot;fix-forward-dont-rewrite&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#fix-forward-dont-rewrite&quot; aria-label=&quot;fix forward dont rewrite permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Fix forward, don’t rewrite&lt;/h2&gt;
&lt;p&gt;The instinct is to go into the store and edit those events. I’d steer you away from that. Events are immutable, and, surprising as it sounds, having precise history, bugs included, is a valid scenario. It’s often the only way to see later what really went wrong. I put it this way in the migration section of &lt;a href=&quot;/en/simple_events_versioning_patterns/&quot;&gt;Simple patterns for events schema versioning&lt;/a&gt;: &lt;em&gt;“you should not change the past… even including bugs, is a valid scenario.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Instead, we could append a corrective event:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReservationPriceCorrected&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ReservationPriceCorrected&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    reservationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    previousTotal&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    correctedTotal&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’s the accountant’s move: we don’t rub out a ledger line; we post a correcting entry next to it. Then we rebuild the read models so the downstream views show the corrected total, and fix the bug so it stops producing new ones.&lt;/p&gt;
&lt;p&gt;There’s a side effect that’s easy to miss. The correction is an event, so the next person to open this stream sees that a bug happened, when it was corrected and why. Nobody has to remember, and if we surface it in the UI, the front desk sees it too.&lt;/p&gt;
&lt;h2 id=&quot;if-we-get-the-correction-wrong-too&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#if-we-get-the-correction-wrong-too&quot; aria-label=&quot;if we get the correction wrong too permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;If we get the correction wrong too&lt;/h2&gt;
&lt;p&gt;We might. Say our correction script has its own bug and writes 1501.00 as the room total rather than the reservation total.&lt;/p&gt;
&lt;p&gt;The recovery is the same operation as before, because the 14 March events are still there. We read the original calculation, the rate plan version that applied, our correction and its &lt;code class=&quot;language-text&quot;&gt;buildSha&lt;/code&gt;, and we append a second correction computed from the same inputs we had the first time. Nothing we did on 19 March destroyed the material we need on 2 April.&lt;/p&gt;
&lt;p&gt;That’s the loop from the state-based version broken. Each attempt adds a fact rather than replacing the evidence, so attempt three is calculated from the booking, not from attempt two.&lt;/p&gt;
&lt;h2 id=&quot;the-customer-already-fixed-it&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-customer-already-fixed-it&quot; aria-label=&quot;the customer already fixed it permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The customer already fixed it&lt;/h2&gt;
&lt;p&gt;Another reservation, four nights, two guests, 29 March to 2 April, at 210.00 a night. Rooms 840.00, tax charged 56.00, correct tax 41.00.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;| when         | what                            |                                            |
|--------------|---------------------------------|--------------------------------------------|
| 14 Mar 09:41 | `ReservationPriceCalculated`    | cityTax 56.00, total 896.00, build a4f9c2e |
| 15 Mar 11:20 | guest calls the hotel           | &quot;that&apos;s not what I was quoted&quot;             |
| 15 Mar 11:26 | `ReservationPriceCorrected`     | total 839.00, by anna.k                    |
| 17 Mar 08:03 | `ReservationConfirmationResent` |                                            |
| 19 Mar 16:02 | we deploy the fix               |                                            |
| 19 Mar 17:30 | we run the bulk correction      |                                            |&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Anna’s reason for that correction reads: &lt;em&gt;city tax recalculated to 41.00, 5% goodwill on rooms agreed with FO manager&lt;/em&gt;. Our recalculation gives 881.00, and it’s correct by the pricing rules. Her 839.00 is also correct, because she took 42.00 off the rooms to settle an argument she was having on the phone.&lt;/p&gt;
&lt;p&gt;If our bulk correction is a blind recalculation, at 17:30 it sets the reservation back to 881.00 and undoes her. The guest gets a third number in a third email and calls again. Support opens the reservation, sees the current total, and has no idea why it changed or that it was us. Anna’s note now describes a value that no longer exists.&lt;/p&gt;
&lt;p&gt;With the stream, logically the check could look like in pseudo code (hey TS, no pun intended):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;needsCorrection&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ReservationEvent&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; bad &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findIndex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
      e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ReservationPriceCalculated&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
      e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buildSha &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BROKEN_BUILD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bad &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// someone already dealt with it, leave it alone&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;events
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bad &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ReservationPriceCorrected&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Everything it skips goes on a list for a person to look at. If we’d rather model the goodwill as its own &lt;code class=&quot;language-text&quot;&gt;DiscountGranted&lt;/code&gt; event, the rule becomes “skip if any price-affecting event follows the bad one”, which is the safer version anyway.&lt;/p&gt;
&lt;p&gt;We can write that check because the events are there to check. In the mutable model, a bug and a deliberate correction look identical: a number in a column. There’s nothing to branch on, unless we can reconstruct intent from an audit table, assuming the support tool writes to one.&lt;/p&gt;
&lt;p&gt;We could do such a check and fix as part of the one-time async job; we could even generalise it and run it as the SQL migration, running it only if it wasn’t run. We could optimise it by loading multiple reservations at once, then doing bulk append, if our store allows that, but the logical work remains the same. The rest is mostly performance optimisation.&lt;/p&gt;
&lt;h2 id=&quot;make-the-correction-a-feature&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#make-the-correction-a-feature&quot; aria-label=&quot;make the correction a feature permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Make the correction a feature&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/6c34b3d35c96ee0e2178a275e5e7e591/2026-07-27-cover.gif&quot; alt=&quot;cover&quot;&gt;&lt;/p&gt;
&lt;p&gt;Better still, treat the corrective operation as a requirement and build it before we need it.&lt;/p&gt;
&lt;p&gt;Ask the business &lt;em&gt;“how do you fix this today?”&lt;/em&gt;. There’s almost always an existing answer: a form, a manager’s approval, a note in the folder. Model that. &lt;code class=&quot;language-text&quot;&gt;CorrectReservationPrice&lt;/code&gt; as a command, with permissions, validation and a mandatory reason, producing &lt;code class=&quot;language-text&quot;&gt;ReservationPriceCorrected&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Our bulk fix then goes through that command, with the same rules and the same events as when Anna runs it from the front desk. Nobody connects to the production database at 23:00.&lt;/p&gt;
&lt;p&gt;And when the business says something can never happen, the follow-up is still: fine, how often?&lt;/p&gt;
&lt;p&gt;There’s a harder version of this. I once watched a hotel checkout get stuck on a blocked financial account, which then jammed the entire night audit, with no way out except a migration or a hotfix. That’s the worst place to end up, telling a customer they can’t operate until we ship code. Bugs, bad data and stuck processes aren’t edge cases we can design away, which is the argument in &lt;a href=&quot;/en/no_it_can_never_happen/&quot;&gt;No, it can never happen!&lt;/a&gt;: &lt;em&gt;“We cannot assume that we won’t have a bug, or our hardware or network won’t have any random failure.”&lt;/em&gt; And if there’s no way to correct something inside the application, someone will do it with an &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt; at 23:00. Read the horror story described there.&lt;/p&gt;
&lt;p&gt;And check &lt;a href=&quot;/en/what_texting_ex_has_to_do_with_event_driven_design/&quot;&gt;What texting your Ex has to do with Event-Driven Design&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;but-now-the-history-contains-a-bug&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#but-now-the-history-contains-a-bug&quot; aria-label=&quot;but now the history contains a bug permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;“But now the history contains a bug”&lt;/h2&gt;
&lt;p&gt;Yes. The wrong price was real. It was shown to the guest, it went into a confirmation, it may have reached the invoice and the tax report. A log that shows only the corrected price can’t explain why the guest called.&lt;/p&gt;
&lt;p&gt;If we need a clean stream later, for a regulator or a migration, still don’t edit in place. Copy and transform into a new stream, and keep the original.&lt;/p&gt;
&lt;p&gt;None of this stops us shipping the bug, makes repricing 900 reservations free, or spares us the conversations with guests whose total changed twice.&lt;/p&gt;
&lt;p&gt;What we get is better material to work with. We can name the events the broken build produced. We can read the value it wrote, with the inputs beside it. And we can see which reservations someone has already handled, and what they agreed to.&lt;/p&gt;
&lt;p&gt;In a state-based system, fixing data overwrites the evidence we’d need to check whether the fix worked, which is how one migration ends up repairing the last one. Keeping the record of the mistake is what lets us check our own work afterwards.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Throw, Result, or neither?]]></title><description><![CDATA[There is one question I keep getting about my event-sourced code: why don’t I use ? It usually comes after someone sees a decision throwing…]]></description><link>https://event-driven.io/en/throw-result-or-neither/</link><guid isPermaLink="false">https://event-driven.io/en/throw-result-or-neither/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5eca6803bd7de5fa9ebc4a238f9b0745/17127/2026-07-20-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAwACBP/EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAFiN43dUn//xAAbEAACAwADAAAAAAAAAAAAAAABAwACEhEhM//aAAgBAQABBQI3yC3iq2DJMp1E+f8A/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGxABAQABBQAAAAAAAAAAAAAAAQAQETEygaH/2gAIAQEABj8C21gAuPkl1j//xAAZEAADAQEBAAAAAAAAAAAAAAAAAREhMUH/2gAIAQEAAT8hXRM7ciHrM570g190OtMRhnp5H//aAAwDAQACAAMAAAAQC9//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAWEQEBAQAAAAAAAAAAAAAAAAABABH/2gAIAQIBAT8QEsv/xAAcEAEBAQABBQAAAAAAAAAAAAABEQAhQVFhkcH/2gAIAQEAAT8QB/WQYKq5KH0xGe9Kpcc0SAOO+JEwq+XnABAg7//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5eca6803bd7de5fa9ebc4a238f9b0745/507e6/2026-07-20-cover.webp 200w,
/static/5eca6803bd7de5fa9ebc4a238f9b0745/28a80/2026-07-20-cover.webp 400w,
/static/5eca6803bd7de5fa9ebc4a238f9b0745/8d2ea/2026-07-20-cover.webp 800w,
/static/5eca6803bd7de5fa9ebc4a238f9b0745/68fc1/2026-07-20-cover.webp 1200w,
/static/5eca6803bd7de5fa9ebc4a238f9b0745/5aa59/2026-07-20-cover.webp 1408w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5eca6803bd7de5fa9ebc4a238f9b0745/37402/2026-07-20-cover.jpg 200w,
/static/5eca6803bd7de5fa9ebc4a238f9b0745/4cda9/2026-07-20-cover.jpg 400w,
/static/5eca6803bd7de5fa9ebc4a238f9b0745/c60e9/2026-07-20-cover.jpg 800w,
/static/5eca6803bd7de5fa9ebc4a238f9b0745/6c738/2026-07-20-cover.jpg 1200w,
/static/5eca6803bd7de5fa9ebc4a238f9b0745/17127/2026-07-20-cover.jpg 1408w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5eca6803bd7de5fa9ebc4a238f9b0745/c60e9/2026-07-20-cover.jpg&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There is one question I keep getting about my event-sourced code: why don’t I use&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt;&lt;strong&gt;?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It usually comes after someone sees a decision throwing an exception. They would put the failure in a &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt;, leave &lt;code class=&quot;language-text&quot;&gt;try/catch&lt;/code&gt; at the application boundary, and make the distinction explicit in the return type. That is a fair question, but the return type is only part of the answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I don’t mind using exceptions. I also return business failures as events.&lt;/strong&gt; Which one I choose depends on what the failure means and what the application needs to do with it. What’s more, I don’t mind Result either, as long as I’m using a language where it’s a native and idiomatic way to use it. Stil…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some failures are worth retaining as business data.&lt;/strong&gt; An out-of-stock request can contribute to an unmet-demand report. A declined payment can start another process or be grouped by its reason. An item limit can explain why customers abandon an operation.&lt;/p&gt;
&lt;p&gt;Gojko Adzic calls the systematic use of unexpected or marginal usage patterns to improve a product &lt;strong&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;lizard optimization&lt;/a&gt;&lt;/strong&gt;. Repeated out-of-stock requests can reveal demand we do not serve or a user experience that encourages impossible quantities. Turning every attempt into only a response or an exception log makes that pattern harder to find. An event gives the application data it can query, project, and use in another process.&lt;/p&gt;
&lt;p&gt;Event Sourcing can help take advantage of unwanted and unexpected failures. We can model negative outcomes as events, just like regular ones. Our business logic informs us of what happened, not whether it’s a success or an error. It’s just an outcome of our decision.&lt;/p&gt;
&lt;p&gt;So essentially we’re getting three options to deal with an unexpected scenario.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;throw an error (e.g. &lt;code class=&quot;language-text&quot;&gt;OutOfStockError)&lt;/code&gt; and catch it at the boundary;&lt;/li&gt;
&lt;li&gt;return a &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt; with the failure on its error track;&lt;/li&gt;
&lt;li&gt;return an event (e.g. &lt;code class=&quot;language-text&quot;&gt;ProductItemOutOfStock&lt;/code&gt;) and decide separately what we do with it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having that, my answer is rarely “just throw” or “just return &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt;.” Before choosing either, I need to know what should be persisted, what the caller should receive, and whether the failure belongs in the application’s exception path.&lt;/p&gt;
&lt;h2 id=&quot;an-error-as-a-business-fact&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#an-error-as-a-business-fact&quot; aria-label=&quot;an error as a business fact permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;An error as a business fact&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Let’s say that the customer asked for a quantity we cannot supply. Recording that outcome can be useful even though the cart did not change. The cart may also reach its item limit or fail payment authorisation. These are expected business outcomes, and the decision can describe each one with a distinct event:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; addProductItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AddProductItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProductItemAdded &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ProductItemOutOfStock &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ShoppingCartItemLimitReached &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Confirmed&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;Cannot add a product to a confirmed shopping cart&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; availableQuantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maximumItems&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; now &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; availableQuantity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemOutOfStock&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        requestedQuantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        availableQuantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        attemptedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; maximumItems&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ShoppingCartItemLimitReached&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        maximumItems&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        requestedProductId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemAdded&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productItem &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ProductItemOutOfStock&lt;/code&gt; carries the requested and available quantities. &lt;code class=&quot;language-text&quot;&gt;ShoppingCartItemLimitReached&lt;/code&gt; carries the configured limit and the product that crossed it.&lt;/p&gt;
&lt;p&gt;We could also model a single &lt;code class=&quot;language-text&quot;&gt;ProductItemAddingFailed&lt;/code&gt; event instead. It could have a &lt;code class=&quot;language-text&quot;&gt;reason&lt;/code&gt; string, but then we’d use the granularity and precision of the information. With distinct event types, a projection, workflow, endpoint, or test can check the relevant outcome directly.&lt;/p&gt;
&lt;p&gt;What’s more, we could also add a FailedToAddProductToClosedShoppingCart and get rid of throwing entirely. We’ll get to that, but for now…&lt;/p&gt;
&lt;h2 id=&quot;broken-rules-can-throw&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#broken-rules-can-throw&quot; aria-label=&quot;broken rules can throw permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Broken rules can throw&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Not every failure needs a business event. A confirmed cart can no longer be modified, so &lt;code class=&quot;language-text&quot;&gt;AddProductItem&lt;/code&gt; is not a valid operation for that state. A command without required data is invalid as well. An unavailable event store, a stock-service timeout, or a serialisation error means the operation could not be completed reliably. I use exceptions for these cases.&lt;/p&gt;
&lt;p&gt;The throw happens before an event is returned, so the handler has nothing to append. The exception retains its stack and reaches the application’s error boundary.&lt;/p&gt;
&lt;p&gt;Some people prefer to use Result and hope for deterministic decision-making. Still, in most popular dev environments (TypeScript, Java, C#, Python, etc.), we can get exceptions. Even in strongly-typed Rust, we can get a panic or other cases. In the presented code &lt;code class=&quot;language-text&quot;&gt;loadShoppingCart&lt;/code&gt; can reject because of data unavailability, &lt;code class=&quot;language-text&quot;&gt;appendToStream&lt;/code&gt; can report a concurrency or connection error, and base code library code can throw on a random, stupid null pointer. The application still needs an error boundary somewhere:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleAddProductItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mapResultToResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mapToErrorResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That boundary might be inside WebAPI route, a message handler callback, or wrapped with a conventional error middleware. The remaining choice is whether expected business outcomes should use the same path.&lt;/p&gt;
&lt;h2 id=&quot;what-result-would-add&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-result-would-add&quot; aria-label=&quot;what result would add permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;What&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt; &lt;strong&gt;would add&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt; puts the success value and the expected failures on separate branches, visible in the type like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Success&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Failure&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Success &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Failure &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The cart decision becomes:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AddProductItemFailure&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ProductItemOutOfStock
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ShoppingCartItemLimitReached&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; addProductItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AddProductItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ProductItemAdded&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; AddProductItemFailure&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; availableQuantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maximumItems&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; now &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; availableQuantity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemOutOfStock&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          requestedQuantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          availableQuantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          attemptedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; maximumItems&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ShoppingCartItemLimitReached&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          maximumItems&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          requestedProductId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemAdded&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productItem &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Technically it looks more or less the same: &lt;code class=&quot;language-text&quot;&gt;ProductItemAdded&lt;/code&gt; is returned on the success branch, while the other two errors are returned on the failure branch. The caller checks the branch before returning the event. In this application, adding the product changes the cart and produces a 204 response. Running out of stock or exceeding the limit leaves the cart unchanged and produces a 409 response. The handler appends only the success value:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handleAddProductItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AddProductItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ProductItemAdded&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; AddProductItemFailure&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadShoppingCart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addProductItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PreconditionFailed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; eventStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendToStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;shoppingCartStreamName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NoContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we used event, it could look like that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handleAddProductItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AddProductItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Result&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ProductItemAdded&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; AddProductItemFailure&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;loadShoppingCart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; event &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addProductItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemOutOfStock&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ShoppingCartItemLimitReached&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PreconditionFailed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; eventStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendToStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;shoppingCartStreamName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NoContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Both versions preserve the same three event types. &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt; adds a success-or-failure classification around them. Which can be fine, but if you look again at the sample, it’s still pretty easy to ignore the different consequences of the events. We’re just one check on success further from it.&lt;/p&gt;
&lt;p&gt;I saw many codebases where people were just blindly muting all errors by default, getting no benefit from it, but just polluting the codebase. Also keeping in mind that they still need to wrap the codebase with try/catch and have a conventional mapping layer.&lt;/p&gt;
&lt;p&gt;We can also see that the Result wrapper in our case doesn’t add much besides success/error classification. If we’d like to return different response status based on the &lt;em&gt;failure&lt;/em&gt; scenario, we’d need to add the same switch. So we’re not removing the decision; we’re just making it easier to ignore.&lt;/p&gt;
&lt;p&gt;Of course, many languages made it streamlined by adding pipe operator etc. Yet, again, many popular environments don’t have it. TypeScript has no pipe operator; the &lt;a href=&quot;https://github.com/tc39/proposal-pipeline-operator&quot;&gt;TC39 proposal&lt;/a&gt; is not (yet?) part of the language. Repeating the branch adds checks throughout the call chain; introducing a helper library adds vocabulary that is not native to the language. Those libraries are extremely noisy and require tribal knowledge and onboarding.&lt;/p&gt;
&lt;p&gt;Scott Wlaschin, who popularised railway-oriented programming, &lt;a href=&quot;https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/&quot;&gt;makes the same qualification&lt;/a&gt;: &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt; models expected alternatives, but it is not intended to wrap every function or replace exceptions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Same for events; we should always discuss with the business if this information is important for them and if they want to keep it.&lt;/strong&gt; We should also ask what should happen and if we can and should recover in our business application when this scenario happens.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ProductItemAdded&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;ProductItemOutOfStock&lt;/code&gt;, and &lt;code class=&quot;language-text&quot;&gt;ShoppingCartItemLimitReached&lt;/code&gt; just tell what has happened. Domain doesn’t need to care how the application handles it. It can mean failure for the current request and still be worth recording because a projection or workflow consumes it. Adding those rules to &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt; would move persistence concerns into the domain decision and add premature classification. When we change the behaviour in the application and record one of those scenarios, we would need to change domain code, even if business rules are the same. Just to formally move one case from failure to success.&lt;/p&gt;
&lt;p&gt;Adding &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt; changes every calling layer without replacing the persistence rules, batch handling, or exception boundary. I do not see much of a gain here. Of course, a codebase that already uses &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt; as its error channel may judge it differently, especially when the alternatives are not events. Thus…&lt;/p&gt;
&lt;h2 id=&quot;returning-an-event-doesnt-mean-persisting-it&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#returning-an-event-doesnt-mean-persisting-it&quot; aria-label=&quot;returning an event doesnt mean persisting it permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Returning an event doesn’t mean persisting it&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;ProductItemOutOfStock&lt;/code&gt; might be needed after the request. Measuring unmet demand, suggesting alternatives, or showing failed attempts to support staff requires a durable event. Still, if our business doesn’t care about the fact and we just want to log it, or forward to the HTTP Response, then we may not need to record it. Appending it would retain data the application does not need. Omitting it from the decision would instead force the endpoint to reread the outcome.&lt;/p&gt;
&lt;p&gt;For this example, the decision returns &lt;code class=&quot;language-text&quot;&gt;ProductItemOutOfStock&lt;/code&gt; to the caller and the stream remains unchanged. I call this &lt;strong&gt;selective persistence&lt;/strong&gt;: the decision describes the outcome, while the application decides whether it should be durable.&lt;/p&gt;
&lt;p&gt;Command Handling can be described by the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;/en/how_to_get_the_current_entity_state_in_event_sourcing/&quot;&gt;Read events from the stream and build the state from them&lt;/a&gt;&lt;/strong&gt; (in other words &lt;em&gt;aggregate stream&lt;/em&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run the business logic using the command and the state.&lt;/strong&gt; Use the default (&lt;em&gt;initial&lt;/em&gt;) state if the stream does not exist.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Append the result of the business logic (so events) at the end of the stream&lt;/strong&gt; from which you’ve read events. Use the read version (or the one provided by the user) for an &lt;a href=&quot;/en/optimistic_concurrency_for_pessimistic_times/&quot;&gt;optimistic concurrency check&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In a nutshell, we could generalise it as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;handleCommand&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  eventStore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  streamId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;decide&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;evolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;initialState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 👇 See what I did here&lt;/span&gt;
  skipEvent&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 1. Get the state&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currentStreamVersion &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; eventStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;aggregateStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 2. Run business logic&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; events &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 3. Filter out &quot;failure&quot; events&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; eventsToAppend &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; skipEvent &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;skipEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 4. Append events&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appendResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; eventStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendToStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    expectedStreamVersion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; currentStreamVersion&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 5. Return result&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;appendResult&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    events&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 👈 all events&lt;/span&gt;
    appendedEvents&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 👈 only those that were appended&lt;/span&gt;
    newState&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;evolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;aggregation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A command handler reads a stream, rebuilds its state, runs a decision, applies the produced events, and appends them with an optimistic concurrency check. &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; takes care of that flow. The application provides &lt;code class=&quot;language-text&quot;&gt;evolve&lt;/code&gt; to apply an event to state and &lt;code class=&quot;language-text&quot;&gt;initialState&lt;/code&gt; for a new stream.&lt;/p&gt;
&lt;p&gt;We can pass &lt;code class=&quot;language-text&quot;&gt;skipEvent&lt;/code&gt; function that inspects the decision’s outcome before anything is appended. The decision still returns the same events; the function only changes how the handler treats them.&lt;/p&gt;
&lt;p&gt;In Emmett, I allow it to encapsulate the Command Handler setup by:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 1. Command handler setup&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;CommandHandler&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ShoppingCart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ShoppingCartEvent&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  middleware&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;skipOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemOutOfStock&quot;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ShoppingCartItemLimitReached&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 2. Usage&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; addProductItem&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I think that this setup clearly shows that we’re separating the business logic from how the business logic handles that, by explicitly using &lt;code class=&quot;language-text&quot;&gt;skipOn&lt;/code&gt; middleware, we’re telling that when such an event happens, we don’t want to store it, but we’ll still return it in the result.&lt;/p&gt;
&lt;p&gt;This gives us the option to still do e.g. error mapping to HTTP status as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  Conflict&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  NoContent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  on&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ResponseFromEvents&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  toWeakETag&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@event-driven-io/emmett-expressjs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;addProductItemApi&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;router&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Router&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&quot;/shopping-carts/:shoppingCartId/product-items&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AddProductItemRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCartId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; productId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; availableQuantity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; inventory&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAvailableQuantity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; command &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AddProductItem&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          availableQuantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          maximumItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; shoppingCartPolicy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maximumItems&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          now&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nextExpectedStreamVersion &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        eventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addProductItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;some&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemAdded&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;PreconditionFailed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NoContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        eTag&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toWeakETag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextExpectedStreamVersion&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We could, of course, do more advanced error or status mapping by returning exact data, etc. But this example proves that we’re not losing anything compared to the Result scenario. The code is still pretty simple, as with throwing, but more explicit and straightforward.&lt;/p&gt;
&lt;h2 id=&quot;several-decisions-on-one-stream&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#several-decisions-on-one-stream&quot; aria-label=&quot;several decisions on one stream permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Several decisions on one stream&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;You may be wondering why I added middleware, which sounds posh for simple filtering. But let’s discuss one more scenario: a batch import from the external e-commerce system.&lt;/p&gt;
&lt;p&gt;A batch import often contains the contents of multiple shopping carts. Passing all of them to one command handler would be the wrong boundary here: each cart has its own stream and its own concurrency check.&lt;/p&gt;
&lt;p&gt;Even a single imported shopping cart can still require several operations. Especially if we’re trying to deduce what has happened to a shopping cart in the external system, or we should add some specific operations on top (e.g., discounts, etc.).&lt;/p&gt;
&lt;p&gt;The common scenario for imports is translating state changes (e.g., products in the shopping cart) into commands. This gives at least a chance to make our domain context meaningful. Not perfect, but we’ve got to save ourselves sometimes.&lt;/p&gt;
&lt;p&gt;After translation, we may end up with a sequence of granular business operations. The mapping could look like that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; importCartCommands&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AddProductItem&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; importedCart&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AddProductItem&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; importedCart&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      availableQuantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; stockByProductId&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      maximumItems&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And feed it into our command handler:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; nextExpectedStreamVersion &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  eventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  importCartCommands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;addProductItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Every command uses the existing &lt;code class=&quot;language-text&quot;&gt;addProductItem&lt;/code&gt; decision and sees the provisional state produced by the previous accepted products. That allows us to apply our own rules and also handle &lt;a href=&quot;/en/idempotent_command_handling/&quot;&gt;idempotence correctly&lt;/a&gt;. Persistence waits until every command has been accepted.&lt;/p&gt;
&lt;p&gt;After batch processing, we can gather the result events. What’s more, each operation should use the state with the applied event from the previous command. We store the result in the shopping carts as a single atomic batch once the last command has been handled.&lt;/p&gt;
&lt;p&gt;Technically it’s the same command logic, just wrapped with foreach, as we now have a sequence of decisions instead of the single one.&lt;/p&gt;
&lt;p&gt;What should happen if we encounter unwanted scenarios? So e.g. &lt;code class=&quot;language-text&quot;&gt;ProductItemOutOfStock&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;ShoppingCartItemLimitReached&lt;/code&gt;? Should we stop processing or continue? Let me be a consultant for a moment: It depends! Fine, but on what? On your business logic.&lt;/p&gt;
&lt;p&gt;In our case, we probably wouldn’t want to stop adding product items if one of them was out of stock. Tho, we may decide that there’s no point in processing it further if we already reached the items limit.&lt;/p&gt;
&lt;p&gt;How to do it? We’d need to define a mapping function with different possible outcomes.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MapDecision&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Event&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;APPEND&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;SKIP&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;STOP&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;REJECT&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;APPEND or no result - success, append this event,&lt;/li&gt;
&lt;li&gt;SKIP - ignore this outcome, don’t append it, but continue processing,&lt;/li&gt;
&lt;li&gt;STOP - ignore this outcome, stop processing and append only those that were already accepted,&lt;/li&gt;
&lt;li&gt;REJECT - reject the whole batch, even if there were some successful events.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The example could be defined for all shopping cart events not to need repeat it in all functions:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;mapShoppingCartDecision&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemOutOfStock&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;SKIP&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ShoppingCartItemLimitReached&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;STOP&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ShoppingCartWasAlreadyConfirmed&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;REJECT&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;    
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We could pass it to our command handler and decide what to do with our processing.&lt;/p&gt;
&lt;p&gt;In Emmett, I wrapped it as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;CommandHandler&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ShoppingCart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ShoppingCartEvent&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  middleware&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;skipOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemOutOfStock&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;stopOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ShoppingCartItemLimitReached&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;rejectOn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ShoppingCartWasAlreadyConfirmed&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Still, as you see, we’re building on top of our business logic. It remains the same; we’re just handling persistence, statuses, etc.&lt;/p&gt;
&lt;p&gt;Returned events show which command handling passed before the rejection and which event stopped the import. The importer can report that outcome against the source record and continue with the next cart, which has a different stream.&lt;/p&gt;
&lt;p&gt;For one product, rejecting meant leaving the stream unchanged. For an imported cart, it may also mean discarding the accepted events produced earlier for the same record.&lt;/p&gt;
&lt;p&gt;If every command is accepted, the handler appends their events together to that cart stream. If one decision returns &lt;code class=&quot;language-text&quot;&gt;ProductItemOutOfStock&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;ShoppingCartItemLimitReached&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;result.events&lt;/code&gt; contains the outcomes produced up to that point, the imported record makes no change to the cart, and later commands are not considered.&lt;/p&gt;
&lt;p&gt;The import job can map those events to a record-level outcome. It can report the exact business issue to the source system and continue with the next cart:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; validatedProductIds &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemAdded&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; failure &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
    event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemOutOfStock&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;
    event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ShoppingCartItemLimitReached&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; importOutcome &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;failure&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ProductItemOutOfStock&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;WaitingForStock&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        validatedProductIds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; failure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        requestedQuantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; failure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;requestedQuantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        availableQuantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; failure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;availableQuantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ShoppingCartItemLimitReached&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Rejected&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        validatedProductIds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; failure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;requestedProductId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        maximumItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; failure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maximumItems&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Imported&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        importedProductIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; validatedProductIds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        streamVersion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextExpectedStreamVersion&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; importStatusStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;importedCart&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;externalId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; importOutcome&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;importOutcome&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Imported&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; salesChannel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;confirmImport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      importedCart&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;externalId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      importOutcome&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;streamVersion&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;WaitingForStock&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pendingImports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;waitForStock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      importedCart&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;externalId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      importOutcome&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Rejected&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; salesChannel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rejectImport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;importedCart&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;externalId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; importOutcome&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      maximumItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; importOutcome&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maximumItems&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A saved-list restore uses different rules. Available products return to the cart, unavailable ones are reported, and reaching the cart limit ends the restore. Products added before either outcome remain valid, so this operation is not atomic.&lt;/p&gt;
&lt;p&gt;The additions run in one call because every decision needs the cart state produced by the accepted products before it. Otherwise, each decision would evaluate the item limit against an outdated cart.&lt;/p&gt;
&lt;p&gt;The loop stages and applies available products. It returns an unavailable product without staging it and continues. It also returns the item-limit event without staging it, then stops because the cart cannot accept another distinct product&lt;/p&gt;
&lt;p&gt;And here we’re getting back to the essential question.&lt;/p&gt;
&lt;h2 id=&quot;so-should-we-throw-or-not&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#so-should-we-throw-or-not&quot; aria-label=&quot;so should we throw or not permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;So should we throw or not?&lt;/h2&gt;
&lt;p&gt;If we’re getting the request from our UI, we’re the authority, so throwing could be fine; we return the status information telling the computer says no. (Read more in &lt;a href=&quot;/en/how_to_validate_business_logic/&quot;&gt;How to validate business logic&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Still, should we throw on an import scenario? Who’s the authority here? What we’re doing is internalising decisions that someone else already made and is just informing us about, or telling us to do our job based on this information.&lt;/p&gt;
&lt;p&gt;So throwing is not a valid option here. The external system told us that its cart contains these products. That is true about the source system, but it does not mean our cart can accept them. The importer translates the external record into commands, and the returned events describe how our local model understood each products.&lt;/p&gt;
&lt;p&gt;For an accepted record, the &lt;code class=&quot;language-text&quot;&gt;ProductItemAdded&lt;/code&gt; events identify the imported products and the resulting stream version is recorded. If stock is missing, the importer records the checked products and the missing quantity. It can continue with other records, then rebuild this cart’s commands with current availability after an inventory update. Retrying the unchanged commands immediately would only reproduce the same outcome. If the cart exceeds its item limit, waiting for stock cannot help; the source record needs correcting, so the importer marks it as rejected.&lt;/p&gt;
&lt;p&gt;Of course we could still throw if this operation is synchronous and we want to give quick feedback to the caller. Still, typically, we run batch operations as a background process; also, to have proper resilience, you already saw above how much integration could happen during such an import. Services we integrate with can be unavailable, have transient issues, time out, etc.&lt;/p&gt;
&lt;p&gt;Those failures enter the import job’s retry or dead-letter policy because there is no reliable business outcome to report yet. The returned events let the application choose between continuing, waiting for a relevant business change, and rejecting one record. Exceptions would just stop processing and not allow recovery and continue with next steps (Read more in &lt;a href=&quot;/en/no_it_can_never_happen/&quot;&gt;No, it can never happen!&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;There can be various error cases also on our side.&lt;/p&gt;
&lt;p&gt;If another request changes the cart before our append, &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; handles the concurrency conflict by loading the stream again and rerunning the decisions against its new state. Decision middleware runs again as well.&lt;/p&gt;
&lt;p&gt;An out-of-stock import is not a concurrency conflict. The cart state did not become stale; the input said that only two items were available. Running the same command again still returns &lt;code class=&quot;language-text&quot;&gt;ProductItemOutOfStock&lt;/code&gt;. The importer waits for an inventory change and then builds a new command with the current availability.&lt;/p&gt;
&lt;p&gt;A custom retry policy can inspect a completed attempt, but it runs after that attempt’s append phase. Retrying a result is safe only when nothing was appended, and useful only when state or input can change.&lt;/p&gt;
&lt;p&gt;A decision and its middleware can therefore run several times during one handler call. Code that only reads the command and state can be rerun. Sending an email or charging a card inside that code would repeat the external effect. In these examples, required external data is fetched before invoking the handler and included in the command. Effects caused by appended events run later in processors, after persistence.&lt;/p&gt;
&lt;p&gt;Middleware could also be useful for authorisation, logging, and measurements. Some of that work belongs around each decision; some should run only once for the complete handler call. Its position relative to the retry boundary determines whether it sees aggregate state and whether a retry repeats it:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;beforeAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;retry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;aggregate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; decision &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; decisions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;runWithDecisionMiddleware&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;decision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;appendSelectedEvents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;afterAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The callback before the retry boundary receives the complete input once. It can authorise the command batch without repeating that work after a concurrency conflict. State is not available yet because the stream has not been loaded.&lt;/p&gt;
&lt;p&gt;Middleware inside the boundary receives each decision, and the state is rebuilt for the current attempt. A concurrency retry runs it again.&lt;/p&gt;
&lt;p&gt;The callback after the boundary receives the final result once, after persistence. It can record measurements based on appended events or the new stream version. If it throws, already appended events remain committed. Validation belongs earlier; notifications that must be delivered need a durable handler or outbox.&lt;/p&gt;
&lt;h2 id=&quot;never-throw-in-asynchronous-handlers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#never-throw-in-asynchronous-handlers&quot; aria-label=&quot;never throw in asynchronous handlers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Never Throw in Asynchronous Handlers&lt;/h2&gt;
&lt;p&gt;As you see, message processing can end up being pretty complex. That’s also why we use Event-Driven Architecture, as it helps to make that explicit and reason about it.&lt;/p&gt;
&lt;p&gt;The import we discussed could be (and probably should be) triggered by an event (e.g. through a webhook), as we’re informed that those items were already processed and we should just accept or ignore it. What has been seen cannot be unseen.&lt;/p&gt;
&lt;p&gt;Whether throwing is safe depends on what sits above the code to catch it. As discussed, a command handler behind an HTTP endpoint, its usual home, has the request: a thrown error unwinds into a response.&lt;/p&gt;
&lt;p&gt;Asynchronous handlers, the projections, reactors, and workflows that react to events, have nothing above them. A throw propagates up, stops the processor, and leaves later events unhandled. The endpoint is only the usual home, not a rule: a command handler run from a reactor loses the same safety net. Instead of throwing, we need to turn the problem into data, so return an event.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A message handler can turn a declined shopping cart operation into a compensating workflow.&lt;/strong&gt; For instance, for &lt;code class=&quot;language-text&quot;&gt;ProductItemOutOfStock&lt;/code&gt; order more products from the producer. It can, of course, just skip a message that should not block processing, or stop without advancing further.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The same for &lt;a href=&quot;/en/projections_and_read_models_in_event_driven_architecture/#idempotency&quot;&gt;read models&lt;/a&gt;.&lt;/strong&gt; A projection cannot prevent the source event from being recorded because that event is already in the stream. Throwing only prevents the read model from advancing. If an event carries a value the read model did not expect, throwing does not undo it. The check that should have stopped it belongs in the business logic, before the event was ever recorded; by the time the projection runs, it is too late.&lt;/p&gt;
&lt;p&gt;It builds a read model from events that are already recorded, and a recorded event is a fact: the projection only interprets it, so it has nothing to reject. If an event carries a value the read model did not expect, throwing does not undo it. The check that should have stopped it belongs in the business logic, before the event was ever recorded; by the time the projection runs, it is too late.&lt;/p&gt;
&lt;p&gt;So accept the event and build the best read model you can from it. Skip a duplicate, fall back to a default, clamp a value back into range, whatever keeps the read model sensible and moving. Its error handling must account for delivery and recovery rather than reuse the rules of a command decision.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The decision returns &lt;code class=&quot;language-text&quot;&gt;ProductItemAdded&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;ProductItemOutOfStock&lt;/code&gt;, or &lt;code class=&quot;language-text&quot;&gt;ShoppingCartItemLimitReached&lt;/code&gt; because the application needs to know which one happened. Out of stock is an unfavourable outcome, but that alone says nothing about storage. During an import it cancels the current source record. During a saved-list restore it is skipped while earlier additions remain. At the HTTP endpoint it becomes a conflict response. The event stays the same; its handling depends on the use case.&lt;/p&gt;
&lt;p&gt;I do not try to remove exceptions from this design. Adding a product to a confirmed cart is an invalid operation. An unavailable event store prevents the handler from producing a reliable result. Both go to the application’s error boundary.&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt; could label some of the returned events as failures, but persistence, continuation, and retry still need their own decisions. The exception path remains as well. The event types already distinguish the business outcomes, so I prefer to leave them as they are.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So when someone asks me whether they should throw an error or use a&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;Result&lt;/code&gt; &lt;strong&gt;type, I sometimes tell: actually, you can do neither.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. if you liked this one, check also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/idempotent_command_handling/&quot;&gt;Idempotent Command Handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/&quot;&gt;Against Railway-Oriented Programming&lt;/a&gt; by Scott Wlaschin&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://file+.vscode-resource.vscode-cdn.net/home/oskar/Repos/emmett/src/docs/guides/error-handling.md&quot;&gt;Emmett: Error Handling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://file+.vscode-resource.vscode-cdn.net/home/oskar/Repos/emmett/src/docs/guides/command-handling.md&quot;&gt;Emmett: Command Handling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;p.s.2. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[On swimming, learning and my journey to being the last one]]></title><description><![CDATA[Work impacts how we live, and our lives impact how we do our work. My name is Oskar Dudycz, not Paulo Coelho, and today I’m not going to…]]></description><link>https://event-driven.io/en/on-swimming-learning-and-my-journey/</link><guid isPermaLink="false">https://event-driven.io/en/on-swimming-learning-and-my-journey/</guid><content:encoded>&lt;p&gt;Work impacts how we live, and our lives impact how we do our work. My name is Oskar Dudycz, not Paulo Coelho, and today I’m not going to talk about software architecture or the event-driven world, but about the world in general, or to be precise, my world. I’ll let you say hello to it, but if you’re looking for a technical piece, that’s not happening today.&lt;/p&gt;
&lt;p&gt;If this story were written in Fakt, Bild, or Sun, it could have been titled:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Trained for 6 years. Finished as the last one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’d be almost true about what has happened to me. Almost would already be a lot for Fakt, Bild or Sun. Now, let’s go with the full story.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/46b005e019977f939962fbaafdf6ac61/10bbb/2026-07-13-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 135.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAIBAwQF/8QAFgEBAQEAAAAAAAAAAAAAAAAAAwIA/9oADAMBAAIQAxAAAAHW6GhCCp0UdGSTkFwpf//EAB4QAQACAQQDAAAAAAAAAAAAAAEAAhEDEhMgIjEy/9oACAEBAAEFAnJPTbKlbIbp5TijROmt9//EABURAQEAAAAAAAAAAAAAAAAAAAEA/9oACAEDAQE/ARmSb//EABcRAAMBAAAAAAAAAAAAAAAAAAABEBH/2gAIAQIBAT8Bc0R//8QAGRABAAMBAQAAAAAAAAAAAAAAABEhQSAx/9oACAEBAAY/AtSqUqa96//EAB4QAQACAgEFAAAAAAAAAAAAAAEAIRExQRBRcYGx/9oACAEBAAE/IaQqriGbTrEyr9Ylt7ZkcZa5icwMa0HyTfjNQ10rXtP/2gAMAwEAAgADAAAAEBsOcP/EABYRAQEBAAAAAAAAAAAAAAAAAAERAP/aAAgBAwEBPxCUJqW4XCM3/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARIf/aAAgBAgEBPxAbrHCGS02//8QAHhABAAICAgMBAAAAAAAAAAAAAQARITFRkRBxgaH/2gAIAQEAAT8QakzZU3cTFpAV4uMIprEGNIYpB+cwHKzjdDtgqwTIrhi9Mo/SaiHOzuI0zdl4ZmPXgFAAron/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/46b005e019977f939962fbaafdf6ac61/507e6/2026-07-13-cover.webp 200w,
/static/46b005e019977f939962fbaafdf6ac61/28a80/2026-07-13-cover.webp 400w,
/static/46b005e019977f939962fbaafdf6ac61/10636/2026-07-13-cover.webp 500w&quot;
              sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/46b005e019977f939962fbaafdf6ac61/37402/2026-07-13-cover.jpg 200w,
/static/46b005e019977f939962fbaafdf6ac61/4cda9/2026-07-13-cover.jpg 400w,
/static/46b005e019977f939962fbaafdf6ac61/10bbb/2026-07-13-cover.jpg 500w&quot;
            sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/46b005e019977f939962fbaafdf6ac61/10bbb/2026-07-13-cover.jpg&quot;
            alt=&quot;2026 07 13 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I’m a weird type of guy, I don’t have a driving license. And neither did I have it. I learned to ride a bike when I was 9 years old and got it for my First Communion. And I didn’t learn to swim till I was 35. Yes, that happened 6 years ago.&lt;/p&gt;
&lt;p&gt;Did I have some trauma, like drowning? Nah, just didn’t happen. Maybe because my father couldn’t swim, maybe because I didn’t have motivation. Dunno, just didn’t happen.&lt;/p&gt;
&lt;p&gt;Of course, not having certain skills can be fine; you can learn to live without them. You can even be considered a fashionable hipster. Some folks consider my lack of a driver’s license as a personal worldview stand. The fact that I like to cycle or ride a train suggests to some that I’m an ecologist. And don’t get me wrong, I like nature, I try to be a good citizen, but that’s not my main motivation.&lt;/p&gt;
&lt;p&gt;This is just what I do, not who I am.&lt;/p&gt;
&lt;p&gt;So am I a swimmer now? Well, it seems that I can swim now. I took classes from 2018 to 2019, then stopped for about 3 years, and got back into it when I signed my daughter up for swimming classes.&lt;/p&gt;
&lt;p&gt;Now, when I say I’m practising swimming, everyone assumes I’m a triathlete preparing for a competition. And well, I’m not.&lt;/p&gt;
&lt;p&gt;I don’t even like to swim much. I mean, it’s fine, I don’t mind, but it’s not that it’s my passion. I don’t have thoughts like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hell, I’d like to swim now.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And I still have thoughts like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hell, I’d play football now.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Which I don’t do, as I don’t play football anymore. Even though it was my passion for most of my life, I wasn’t talented at it, but I liked doing it. I even played as a junior for my hometown club (Miedź Legnica) and later in amateur leagues. And in those amateur leagues, I tore something called the &lt;em&gt;lateral patellar retinacula&lt;/em&gt;. Actually, two of them, in both knees. Which means that my knees are unstable, and if I continued to play, it’d end up with some challenging surgery.&lt;/p&gt;
&lt;p&gt;That also means that I had to decide to be unsuccessful somewhere else.&lt;/p&gt;
&lt;p&gt;So, I’m an unsuccessful footballer and now an unsuccessful swimmer in the making.&lt;/p&gt;
&lt;p&gt;I tried running and going to the gym, but it was always boring for me. I could do it for a few months, but then I quit. And off we go, for not training.&lt;/p&gt;
&lt;p&gt;And I tried swimming.&lt;/p&gt;
&lt;p&gt;Why? We don’t need to be capable of everything; it’s often a fine choice not to learn everything. At some point, a lack of certain skills can be limiting. You already know that it wasn’t my worldview to always have my feet on solid ground, but the main motivations were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s nice to do some swimming on holidays,&lt;/li&gt;
&lt;li&gt;It’s NOT nice to be a lame dad who can’t swim with his daughter,&lt;/li&gt;
&lt;li&gt;I kindaish always wanted to learn that, just didn’t happen,&lt;/li&gt;
&lt;li&gt;It’d be good to do some exercises, keeping in mind my sitting style of work,&lt;/li&gt;
&lt;li&gt;I had a swimming pool 10m by walk,&lt;/li&gt;
&lt;li&gt;I had and still have a great teacher. Thanks, Justyna!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I started learning to swim again in 2024. It’s an interesting exercise when you learn something new as an adult. It’s more challenging because you’re overthinking rather than acting on your instincts. Or maybe your old habits are making your instincts unintuitional. It also shows how everyone has a different way of learning.&lt;/p&gt;
&lt;p&gt;Take Breaststroke style (in Polish called “Żabka” - frog). Most people claim that it’s the easiest style to learn. For me? Madness, how much stuff you need to remember and coordinate: hands, back, legs.&lt;/p&gt;
&lt;p&gt;It also shows how everything is connected. It appeared that I couldn’t do it properly because I wasn’t stretched enough. Only after I started stretching was I able to move my back and twist my legs correctly. Well almost. Almost in the Fakt, Bild and Sun way.&lt;/p&gt;
&lt;p&gt;Ok, so I learned to swim kindaish. But the best progress I made was last year.&lt;/p&gt;
&lt;p&gt;When we started the season in September, my coach told our group that we’d start competing. I said hell yeah, and Justyna took the irony for a real agreement. So it started.&lt;/p&gt;
&lt;p&gt;It appeared that the shortest possible distance is 475 meters, which is around 19 swimming pool lengths. Ah, and it’s on the lake, deep for 22 meters. I didn’t know about that last part when I signed for it. I also didn’t know if I could swim for so long with the crawl style. I had probably a record of around 6-8 swimming pools.&lt;/p&gt;
&lt;p&gt;And well, I made it.&lt;/p&gt;
&lt;p&gt;Was I stupid or just dumb? I hope that neither of those. I used the best way to learn: practical and deadline-driven.&lt;/p&gt;
&lt;p&gt;Instead of one training per week, I started doing two. I took some private lessons with Justyna and also started doing longer swimming sessions. Slowly but surely, I beat my record to 10, 15, 20, then 40.&lt;/p&gt;
&lt;p&gt;Yes, it was stressful; yes, it required a patient, slow process, but it gave me at least the trust in myself that if I can swim 40, then I’ll probably make 19.&lt;/p&gt;
&lt;p&gt;It also helped to do a real dry run in the lake and the sea. If you only swim in the swimming pool, you’ll be surprised how different swimming in a lake is.&lt;/p&gt;
&lt;p&gt;The water is dirty; it’s super easy to lose your bearings and go in a longer circle than you should.&lt;/p&gt;
&lt;p&gt;And guess what? The real competition brings even more surprises. The crowd of other swimmers is a challenge. Constant pushing on the start, distracting from catching the flow. Then stress, and moments of doubt about yourself. You can prepare for that, but only to some degree; the real learning comes through the next stage, which starts in competition.&lt;/p&gt;
&lt;p&gt;Were I actually the last one? No, I was 53 out of 59. Seems I wasn’t all bad. Just half bad. I had 14m 20s time. My best on practice was 12m.&lt;/p&gt;
&lt;p&gt;Still, not too bad for a guy who learned to swim at 35, if you ask me. I’m super proud of myself.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://talesfrom.dev/about&quot;&gt;My friend Damian&lt;/a&gt; (jokingly) told me that I should write this as&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“10 things I learned about EDA from practicing swimming and preparing for a competition”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And that’s not going to happen, but let me give an inspirational quote from the man, the legend, Michael Jordan:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I don’t compete with other people. I compete with what I’m capable of.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And that’s what I did. Of course, Michael wasn’t competing with others, as he was the best, GOAT. I wasn’t competing, as I was one of the worst.&lt;/p&gt;
&lt;p&gt;Yet, I was still much better than my past self. And I also achieved more than those who haven’t even tried.&lt;/p&gt;
&lt;p&gt;Interestingly, I was sometimes perceived as humble, and sometimes as too self-assured. Maybe it comes from a common answer when someone asks me if I can do something:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I don’t know; I haven’t tried it yet.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But I think that stems from my belief that we are all human. We’re not worse than others, but we’re also not better than others, which can be seen as bold or humble, depending on the focus of this belief.&lt;/p&gt;
&lt;p&gt;We won’t know what we’re capable of until we try it. And when we do, we’ll already be much further than your past selves that haven’t tried.&lt;/p&gt;
&lt;p&gt;I’m not trying to say that we can achieve everything; I probably won’t ever be a good swimmer. If I could achieve that, then anyone can. I’m not saying that’s going to be easy. It’s not. It requires time. And time is a precious resource. Not infinite. The best way to find it is to find it. But to do it, you need to set a priority, which means something else will become less important.&lt;/p&gt;
&lt;p&gt;Pick wisely.&lt;/p&gt;
&lt;p&gt;Should you also swim? Well, maybe. You definitely should do some exercises if you’re a developer like me. But precisely swimming? Up to you. Should you get a hobby other than work? Definitely.&lt;/p&gt;
&lt;p&gt;Why did I write this article? A bit to brag, as I’m happy I made it.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ceb330afae3784b17d34f0af4b9f4e87/6c738/2026-07-13-after_competition.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 133.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGQAAAwEBAQAAAAAAAAAAAAAAAAIEBQED/8QAFwEBAQEBAAAAAAAAAAAAAAAAAQMAAv/aAAwDAQACEAMQAAABpfOZLjPDNNz0nRSYef/EAB4QAAEEAQUAAAAAAAAAAAAAAAEAAgMREhATMTJC/9oACAEBAAEFAlemTsT1pF+4BdNkcouPBX//xAAYEQACAwAAAAAAAAAAAAAAAAAAEBESQf/aAAgBAwEBPwGpBi//xAAYEQACAwAAAAAAAAAAAAAAAAAAEgEQMf/aAAgBAgEBPwFhydr/xAAfEAACAAUFAAAAAAAAAAAAAAAAARARITEyAmFxgZH/2gAIAQEABj8ChafYq1FJsyZsi/pkkPg1Q//EABwQAAICAwEBAAAAAAAAAAAAAAABESExQXFRgf/aAAgBAQABPyFOKFBKyfOzEO2V0Ue1L2hM9BIxwEmc31gRXrwN8hmXTRkf/9oADAMBAAIAAwAAABD3xzD/xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQMBAT8QpbNpX//EABgRAAMBAQAAAAAAAAAAAAAAAAABIREx/9oACAECAQE/EMDVRi5B2n//xAAkEAEBAAIAAwkBAAAAAAAAAAABEQAhMUGBEFFhcZGhscHw8f/aAAgBAQABPxAdlLvyyKJ5K8TEjABr+jFdGw3W33wKGNYFOOnrhamzEHFUCggZpI9NBMDGRISB3H7liMak8L9YxLmzwwL9oD0vz2f/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/ceb330afae3784b17d34f0af4b9f4e87/507e6/2026-07-13-after_competition.webp 200w,
/static/ceb330afae3784b17d34f0af4b9f4e87/28a80/2026-07-13-after_competition.webp 400w,
/static/ceb330afae3784b17d34f0af4b9f4e87/8d2ea/2026-07-13-after_competition.webp 800w,
/static/ceb330afae3784b17d34f0af4b9f4e87/68fc1/2026-07-13-after_competition.webp 1200w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/ceb330afae3784b17d34f0af4b9f4e87/37402/2026-07-13-after_competition.jpg 200w,
/static/ceb330afae3784b17d34f0af4b9f4e87/4cda9/2026-07-13-after_competition.jpg 400w,
/static/ceb330afae3784b17d34f0af4b9f4e87/c60e9/2026-07-13-after_competition.jpg 800w,
/static/ceb330afae3784b17d34f0af4b9f4e87/6c738/2026-07-13-after_competition.jpg 1200w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/ceb330afae3784b17d34f0af4b9f4e87/c60e9/2026-07-13-after_competition.jpg&quot;
            alt=&quot;2026 07 13 after competition&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Brag about being 53 out of 59 after 6 years of training? Seems so! You need to cherish small successes that, from the right perspective, appear to be big ones.&lt;/p&gt;
&lt;p&gt;So, are there 10 things you can learn about software engineering from practising swimming and preparing for a competition?&lt;/p&gt;
&lt;p&gt;I don’t know; you tell me.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. if you liked this one, check also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/borys_najlepiej_dryblowal/&quot;&gt;Borys had the best dribbling&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_playing_on_guitar_helps_in_being_better_developer/&quot;&gt;How playing on guitar can help you to be a better developer?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/a_few_words_about_workaholism/&quot;&gt;Don’t be like Ebenezer Scrooge. A few words about workaholism&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;p.s.2. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Addition by subtraction in Software Design]]></title><description><![CDATA[I want it all, and I want it now. This is not only a Freddie Mercury quote but also, too often, a general plan for our software product. The…]]></description><link>https://event-driven.io/en/addition-by-subtraction-in-software-design/</link><guid isPermaLink="false">https://event-driven.io/en/addition-by-subtraction-in-software-design/</guid><content:encoded>&lt;p&gt;I want it all, and I want it now. This is not only a Freddie Mercury quote but also, too often, a general plan for our software product. The picture tells more than words, so here it is, the typical product we designed:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e21f3e6a6f81e7da9dce92657435fa1f/d0606/2026-07-06-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAMABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAUDBP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAa3BRBmP/8QAGxAAAgIDAQAAAAAAAAAAAAAAAQIAAxESEzL/2gAIAQEAAQUCdxWOlhfWelUARmOf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFxABAQEBAAAAAAAAAAAAAAAAACEQIv/aAAgBAQAGPwJyq3f/xAAbEAEAAgMBAQAAAAAAAAAAAAABABEhMUEQYf/aAAgBAQABPyEuJvUDjJ6ciGuqzCqyCK4Na+eT/9oADAMBAAIAAwAAABDjD//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABoQAQADAQEBAAAAAAAAAAAAAAEAESFBMVH/2gAIAQEAAT8Qa0eUHucfsRVDMbUa2y8AywdlG8ViTFTvRuuS00n/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/e21f3e6a6f81e7da9dce92657435fa1f/507e6/2026-07-06-cover.webp 200w,
/static/e21f3e6a6f81e7da9dce92657435fa1f/28a80/2026-07-06-cover.webp 400w,
/static/e21f3e6a6f81e7da9dce92657435fa1f/8d2ea/2026-07-06-cover.webp 800w,
/static/e21f3e6a6f81e7da9dce92657435fa1f/a35e2/2026-07-06-cover.webp 970w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/e21f3e6a6f81e7da9dce92657435fa1f/37402/2026-07-06-cover.jpg 200w,
/static/e21f3e6a6f81e7da9dce92657435fa1f/4cda9/2026-07-06-cover.jpg 400w,
/static/e21f3e6a6f81e7da9dce92657435fa1f/c60e9/2026-07-06-cover.jpg 800w,
/static/e21f3e6a6f81e7da9dce92657435fa1f/d0606/2026-07-06-cover.jpg 970w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/e21f3e6a6f81e7da9dce92657435fa1f/c60e9/2026-07-06-cover.jpg&quot;
            alt=&quot;Swiss knife&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;And, well, that’s probably not what our user wanted. They might just want a sharper steak knife, so something more like:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/81e3d2fd0b0029983bdb275eb6529f2e/609ff/2026-07-06-steak-knife.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAEDBAL/xAAVAQEBAAAAAAAAAAAAAAAAAAABAv/aAAwDAQACEAMQAAABw6a4Gx0TIJYFH//EABoQAQEAAgMAAAAAAAAAAAAAAAEAEjECESH/2gAIAQEAAQUC6YJ8ciOVkM703//EABgRAAIDAAAAAAAAAAAAAAAAAAEQETFB/9oACAEDAQE/ARGo2V//xAAVEQEBAAAAAAAAAAAAAAAAAAAQAf/aAAgBAgEBPwEh/8QAGxAAAQQDAAAAAAAAAAAAAAAAEAABESESIEH/2gAIAQEABj8CZR0ReIvT/8QAHBABAAMAAgMAAAAAAAAAAAAAAQARITFBUWFx/9oACAEBAAE/Ieh3CQpPAYCNUtPIxdctlAWXX2LfqJZUtn//2gAMAwEAAgADAAAAEMfAfP/EABcRAQADAAAAAAAAAAAAAAAAABABMaH/2gAIAQMBAT8QnQbD/8QAFxEBAAMAAAAAAAAAAAAAAAAAAREgMf/aAAgBAgEBPxBnSn//xAAdEAEAAgICAwAAAAAAAAAAAAABABEhMUFRocHR/9oACAEBAAE/ECpANhdMT6sGvcC2JI48kcHYPkdSmotKEOMaibDcWeUxPGkqPaBYGf/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/81e3d2fd0b0029983bdb275eb6529f2e/507e6/2026-07-06-steak-knife.webp 200w,
/static/81e3d2fd0b0029983bdb275eb6529f2e/28a80/2026-07-06-steak-knife.webp 400w,
/static/81e3d2fd0b0029983bdb275eb6529f2e/8d2ea/2026-07-06-steak-knife.webp 800w,
/static/81e3d2fd0b0029983bdb275eb6529f2e/68fc1/2026-07-06-steak-knife.webp 1200w,
/static/81e3d2fd0b0029983bdb275eb6529f2e/4004c/2026-07-06-steak-knife.webp 1500w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/81e3d2fd0b0029983bdb275eb6529f2e/37402/2026-07-06-steak-knife.jpg 200w,
/static/81e3d2fd0b0029983bdb275eb6529f2e/4cda9/2026-07-06-steak-knife.jpg 400w,
/static/81e3d2fd0b0029983bdb275eb6529f2e/c60e9/2026-07-06-steak-knife.jpg 800w,
/static/81e3d2fd0b0029983bdb275eb6529f2e/6c738/2026-07-06-steak-knife.jpg 1200w,
/static/81e3d2fd0b0029983bdb275eb6529f2e/609ff/2026-07-06-steak-knife.jpg 1500w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/81e3d2fd0b0029983bdb275eb6529f2e/c60e9/2026-07-06-steak-knife.jpg&quot;
            alt=&quot;Steak knife&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I’m also to blame; I’m falling into precisely the same trap too often. Like recently, while working on &lt;a href=&quot;/en/announcing-strictland-contract-testing&quot;&gt;Strictland, the contract testing library I announced in the previous article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The idea behind it is to detect message schema drift easily. You write such a test:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;orderPlacedSchema_DoesNotChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Strictland specification&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;MessageContract&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;specification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jackson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OrderPlaced&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; placedAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;whenSerialized&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenContractIsUnchanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It serialises the message and stores the payload inside the same git repository as the test. Thanks to that, you can keep it close and detect contract changes during the review by looking at the changed files.&lt;/p&gt;
&lt;p&gt;Initially, files were kept in the same folder as the test file. That made it accessible, as you don’t need to jump from one folder to another and see related test files. The file was named by message type class name (so in this case it’d be &lt;code class=&quot;language-text&quot;&gt;OrderPlaced.approved.text&lt;/code&gt;). Simple, but what if someone adds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple tests, trying to store different snapshots for the same message type?&lt;/li&gt;
&lt;li&gt;Multiple test files dispersed across different folders that test the same message type (e.g., in the features that consume those messages)?&lt;/li&gt;
&lt;li&gt;Multiple message types across different modules/packages with the same name?&lt;/li&gt;
&lt;li&gt;Multiple tests for different message-type versions and variants (e.g., with all fields, with only required fields, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of that is solvable by some conventions, but what if you’re an open-source library creator like me? Then you can’t assume the same type of thinking, level of tidiness or knowledge of your users. You should be the one to take on the pain and make your users’ lives easier. And guess what? That applies not only to open source, but also to other products.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“Taking the pain”&lt;/em&gt; and &lt;em&gt;“helping users”&lt;/em&gt; can be interpreted as an attempt to please everyone, which translates to the mentioned earlier: “I want it all, and I want it now.”&lt;/p&gt;
&lt;p&gt;That’s what I tried to do. I decided to add &lt;em&gt;“selectable storage layouts strategies”&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Near the test file&lt;/strong&gt; - so similar to the one we had,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In the nested folder near the test file -&lt;/strong&gt; similar, but separating the snapshot files from tests, thanks to that, you could look in the nested folder, keeping the tests not polluted by the numerous tests,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In the root folder&lt;/strong&gt; - putting it close to the root folder, keeping snapshots together- still the same repo but giving potentially better accessibility.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And &lt;em&gt;”selectable storage layouts strategies”&lt;/em&gt; were not enough for me. I also wanted to add &lt;em&gt;“various grouping strategies”&lt;/em&gt; where one could select whether they want to group message snapshots by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Message type name,&lt;/li&gt;
&lt;li&gt;Test files and their names.&lt;/li&gt;
&lt;li&gt;Or just apply no grouping.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It sounded smart to me, and hell, with GenAI, I wouldn’t even need to fight with it, as my friend Claude would do it for me, right? &lt;a href=&quot;/en/requiem-for-a-10x-engineer-dream/&quot;&gt;RIGHT?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 625px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/326242b6af0ce434b8e47b5c0d9cebde/a8e68/2026-07-06-thats-what-she-said.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAIBAwT/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABpdGXQSJ//8QAHBAAAQMFAAAAAAAAAAAAAAAAAQIDMgAREiJD/9oACAEBAAEFAgo2ypsadHYJj//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABkQAQEBAAMAAAAAAAAAAAAAAAEAIRAiQf/aAAgBAQAGPwJ6m+tgPBMX/8QAHBAAAgICAwAAAAAAAAAAAAAAAREAMSFBUWGR/9oACAEBAAE/IQkPysUcBNhu4MgwpQdGYqAvjuH5Cf/aAAwDAQACAAMAAAAQMO//xAAXEQADAQAAAAAAAAAAAAAAAAABEBEx/9oACAEDAQE/EMEX/8QAFhEAAwAAAAAAAAAAAAAAAAAAACEx/9oACAECAQE/EKys/8QAHxABAQACAAcBAAAAAAAAAAAAAREAITFBUWFxgZGh/9oACAEBAAE/EES62PgJgUMtdB7mRDYpBv7ixgpESjxxCgowQCdryxujW14M/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/326242b6af0ce434b8e47b5c0d9cebde/507e6/2026-07-06-thats-what-she-said.webp 200w,
/static/326242b6af0ce434b8e47b5c0d9cebde/28a80/2026-07-06-thats-what-she-said.webp 400w,
/static/326242b6af0ce434b8e47b5c0d9cebde/ee9b0/2026-07-06-thats-what-she-said.webp 625w&quot;
              sizes=&quot;(max-width: 625px) 100vw, 625px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/326242b6af0ce434b8e47b5c0d9cebde/37402/2026-07-06-thats-what-she-said.jpg 200w,
/static/326242b6af0ce434b8e47b5c0d9cebde/4cda9/2026-07-06-thats-what-she-said.jpg 400w,
/static/326242b6af0ce434b8e47b5c0d9cebde/a8e68/2026-07-06-thats-what-she-said.jpg 625w&quot;
            sizes=&quot;(max-width: 625px) 100vw, 625px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/326242b6af0ce434b8e47b5c0d9cebde/a8e68/2026-07-06-thats-what-she-said.jpg&quot;
            alt=&quot;That&amp;#39;s what she said meme&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Now, &lt;em&gt;”selectable storage layouts strategies”&lt;/em&gt; should sound enough as a warning to me and &lt;em&gt;“various grouping strategies”&lt;/em&gt; should ring a loud alarm bell. But it didn’t. I planned how to do it and started the implementation.&lt;/p&gt;
&lt;p&gt;A few iterations later, the permutations of those &lt;em&gt;various strategies&lt;/em&gt; started to pile up. Some of them were just hard to do. Some were conflicting. And most of them weren’t even useful.&lt;/p&gt;
&lt;p&gt;Examples? I decided to make all files follow the unified format:&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;{messageTypeName}.{version}.{variant}.snap.approved.{extension}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And that’s fine, but then what to use as the message type name? Class name? Fine, but what about a flat layout, or grouping by message type name, with multiple messages of the same name?&lt;/p&gt;
&lt;p&gt;Let’s use the full class path as the name? Fine, but then what if you have a root folder and a nested folder strategy? Would you nest folders by the class path, and then repeat it in the name?&lt;/p&gt;
&lt;p&gt;Or maybe you’d differentiate the file name format depending on the strategy one chose?&lt;/p&gt;
&lt;p&gt;Ok, but what if the user decides to change the strategy at some point?&lt;/p&gt;
&lt;p&gt;I decided to run a sanity check I should have done earlier. Better later than too late. The discussion inside my head looked more or less like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Me:&lt;/strong&gt; What’s the actual goal of this library?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I:&lt;/strong&gt; To detect message schema drift.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Me:&lt;/strong&gt; Ok, then do I need to build the snapshot library?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I:&lt;/strong&gt; To some degree, I’m using snapshot comparison to detect that drift.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Me:&lt;/strong&gt; Sure, but I probably don’t need to cover all the cases that the generic snapshot library has to, right?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I:&lt;/strong&gt; Right.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Me:&lt;/strong&gt; Ok, so what’s our bet for users? Are they advanced or just beginning the journey?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I:&lt;/strong&gt; At the beginning.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Me:&lt;/strong&gt; Then I should help them, set a sane default, and guide them. Especially since this is the early phase of this library, I haven’t yet received much feedback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I:&lt;/strong&gt; Agreed, maybe we could start then with the recommended layout and grouping, and then gather feedback. I can add more strategies as needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That sounds like an obvious thing to do, but it requires pausing to find the time to evaluate our assumptions. We shouldn’t always be mad at ourselves for not predicting everything earlier. Some stuff is just hard to predict. Personally, I usually see some steps after I try to implement them. Those are also the steps I explained in the &lt;a href=&quot;/en/vibing_harness_and_ooda_loops/&quot;&gt;article about the OODA loop&lt;/a&gt;. We should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Observe&lt;/strong&gt; - This is the intake of raw, unfiltered information. In our world, this means looking at the state of the system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orient&lt;/strong&gt; - This is the most critical and difficult stage. It’s where we filter our observations through your experience, culture, and technical knowledge.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decide&lt;/strong&gt; - Based on our orientation, formulate a hypothesis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Act&lt;/strong&gt; - We execute.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rinse repeat.&lt;/p&gt;
&lt;p&gt;If we think about it this way, the time spent considering those various cases is not wasted. It’s similar to the &lt;a href=&quot;https://particular.net/blog/antirequirements&quot;&gt;anti-requirements exercise described by Andreas Öhlund and David Boike&lt;/a&gt; or &lt;a href=&quot;/en/residuality-theory-a-rebellious-take/&quot;&gt;Barry O’Reilly’s Residuality Theory&lt;/a&gt;. We gather numerous needs and ideas about our product evolution and see what sticks and what remains. Not all ideas will be great; some will be bad, but that doesn’t matter; it gives us food for thought.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, we can’t forget about the most important part: subtraction.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We need to subtract all the stuff that’s not needed, leaving only the useful residues, the one that solves the main goal.&lt;/p&gt;
&lt;p&gt;For my case, I realised that if I chose the single strategy, it would organise my contract into the root test folder like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 src/test/resources/
  📁 contract-registry/
    📁 com/acme/orders/OrderPlaced/
      📄 OrderPlaced.1.default.snap.approved.json
      📄 OrderPlaced.2.default.snap.approved.json
    📁 com/acme/orders/OrderInitiated/
      📄 OrderInitiated.1.AllData.snap.approved.json
      📄 OrderInitiated.1.OnlyRequired.snap.approved.json
      📄 OrderInitiated.2.AllData.snap.approved.json
      📄 OrderInitiated.2.OnlyRequired.snap.approved.json
    📁 InvoiceIssuedEvent/
      📄 InvoiceIssuedEvent.1.default.snap.approved.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By nesting by message type and full path and grouping by contract name, I’m not only keeping snapshots but also creating a Contract Registry.&lt;/p&gt;
&lt;p&gt;So this subtraction of some features not only gave me focus on the specific scenario but also enabled me to create scenarios that I would actually like to have eventually, for instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;keep the message schema in the same folder as snapshots,&lt;/li&gt;
&lt;li&gt;markdown docs of the message,&lt;/li&gt;
&lt;li&gt;Having defined the folder structure, generate documentation,&lt;/li&gt;
&lt;li&gt;check dependency, and who uses that,&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That led me to conclude the following two rules:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The addition of imaginary features subtracts the actual value.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The subtraction of imaginary features adds the actual value.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It’s also aligned with my motto: &lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;Removability over Maintainability&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;whats-the-lesson-then&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#whats-the-lesson-then&quot; aria-label=&quot;whats the lesson then permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What’s the lesson then?&lt;/h2&gt;
&lt;p&gt;We’re not wasting time on building too slowly; we’re wasting time on building stuff that doesn’t matter. All those useless features we imagine without evaluating them with people are a wasted opportunity to deliver better for them.&lt;/p&gt;
&lt;p&gt;Nowadays, with LLMs, it’s tempting to rush into implementation and add every feature we can imagine. That’s never free. We’re increasing the cognitive load for our users and also for us; we waste time on stuff that doesn’t matter and are constantly distracted.&lt;/p&gt;
&lt;p&gt;Yet, if we don’t do sanity checks, trying things in a proof-of-concept way can be a big multiplier. If we stop after that and rethink.&lt;/p&gt;
&lt;p&gt;I’d still advocate doing a sanity check earlier than I did in the described scenario and thinking first, but not all can be forseen like that. So:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate as many ideas as you can.&lt;/li&gt;
&lt;li&gt;Try to think about how they match together, think about the tradeoffs, the cost of adding them, the cost of removing them and the cost of changing your mind.&lt;/li&gt;
&lt;li&gt;Define potential solutions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stop the addition phase&lt;/strong&gt; and rethink what you came up with. Ask yourself what you’re building and what the actual goal is.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Start the subtraction phase&lt;/strong&gt; and cut as much as you can on the stuff that’s actually not needed. Of course, don’t drop ideas, remember that quantification is subjective. &lt;a href=&quot;https://www.youtube.com/watch?v=yV97QwC5gnE&quot;&gt;Deliver a simple solution that will give you enough optionality to change your mind&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Send it wild and gather feedback.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that’s precisely what I did: I just released &lt;a href=&quot;https://github.com/event-driven-io/strictland&quot;&gt;Strictrland 0.4.0&lt;/a&gt; and will plan the next steps based on user feedback!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Announcing Strictland - new contract testing library for message compatibility]]></title><description><![CDATA[I just released something. It’s called Strictland. And it’s a contract testing library. Why did I do it? Before I go further, if you can’t…]]></description><link>https://event-driven.io/en/announcing-strictland-contract-testing/</link><guid isPermaLink="false">https://event-driven.io/en/announcing-strictland-contract-testing/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7b5f1c6c933cf4162365268337a15b75/e67e7/2026-06-15-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC50lEQVR42gHcAiP9AHpVI2xIHX5TJcR2KjovJyQ1NzE6OT5FRTM7PmhLNMKpicC+qVxcVkVJS05QUUhLTUlMTlZYV2lpZ3h3cgBzTCN7VCt/UibDdik7MCc4Q0ZOUE9RV1xSRDjYlF/89Nn//+XJtphJSUZITE45PkI4PUE/Q0Z3eHaVlJAAhFgwmWlIfVEqwXQnRDUuSlJWUFJTVV1gUTgoyoZX+O3M/fja2bGGTUE5NT5FOT5CO0BENzxARkpNg4OAAGxULHRTK6FnKcR2KUM1LjtEST5CRj9ERUcqGItbOseog8OrjL9/UnRXRENLTj1BRDg9QT1BRFNUVYiIhABLNiROMh+KVii+cig7LicqNTk9QEI0OkFKMCGWXDeqfFm1jGi8g1l0ZVhhZWhXWFptbm1sbGpoaWeQjokAPCEbVB0WbDMavXInMSokGysyKzM2OkQ+QDgmtmY5nm5N3LeOr3lIU0wsaF08Y11FcnJshYeDi42Ig4WBAFVAJGEoGXQ6GapjIDklHjY4KUxCHnhVNiUoHHU5GKZrR8mdcZx3TV9IGWlOGnBcHVRMLDozNTUwMElDQQBIKR9dJxtwNBmUUCJBLhheThyFWi6gYkBcWUVLOSOMVzuEXEp/YFleUCFcSR5ZRhxOQhtoQhuSTxyTUh4ASDEjWCshaDElbjcrMy8aNjQUr4dh0J92xZ97jXxnRxYdfkxJeVVTbF0zVkgaSj0bPTcaWU0fo3sy6aM/AFI+JkUeGEwaEUMZETYsFjQwGamCYMqdd3NKLVtHI7Sfgv/00vTu0n1nQVRDFk9EHzM0GFA/GkNCHZ5vMAArERdKGxNdIhJUJhM+NhVsYENlLyFqOihTSSBSQhC4pHz3683++t+Se1dRQRNDPx8gKBYnLxg5ORlNMyAAJQsVTBwTVyESRywVISoUUkAmoGRFRj8vQ0EdTUEXj3lU8+bI//ndnYdnUT0STEIfPDodIyoXOzwedUcXLqX1smJHq04AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/7b5f1c6c933cf4162365268337a15b75/507e6/2026-06-15-cover.webp 200w,
/static/7b5f1c6c933cf4162365268337a15b75/28a80/2026-06-15-cover.webp 400w,
/static/7b5f1c6c933cf4162365268337a15b75/8d2ea/2026-06-15-cover.webp 800w,
/static/7b5f1c6c933cf4162365268337a15b75/68fc1/2026-06-15-cover.webp 1200w,
/static/7b5f1c6c933cf4162365268337a15b75/546af/2026-06-15-cover.webp 1338w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/7b5f1c6c933cf4162365268337a15b75/36ca5/2026-06-15-cover.png 200w,
/static/7b5f1c6c933cf4162365268337a15b75/a3397/2026-06-15-cover.png 400w,
/static/7b5f1c6c933cf4162365268337a15b75/a331c/2026-06-15-cover.png 800w,
/static/7b5f1c6c933cf4162365268337a15b75/8537d/2026-06-15-cover.png 1200w,
/static/7b5f1c6c933cf4162365268337a15b75/e67e7/2026-06-15-cover.png 1338w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/7b5f1c6c933cf4162365268337a15b75/a331c/2026-06-15-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I just released something. It’s called Strictland.&lt;/strong&gt; And it’s a contract testing library. Why did I do it?&lt;/p&gt;
&lt;p&gt;Before I go further, if you can’t wait, you can check it on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub - &lt;a href=&quot;https://github.com/event-driven-io/strictland&quot;&gt;https://github.com/event-driven-io/strictland&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Maven Central - &lt;a href=&quot;https://central.sonatype.com/artifact/io.event-driven/strictland&quot;&gt;https://github.com/event-driven-io/strictland&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;So now you already know that it’s a JVM (Java, Scala, Kotlin, etc.) Open Source library.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let me first show you a sneak peek. Stop for a moment and have a look on this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ensureOrderPlacedCompatibilityWithNewerVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Strictland specification&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;MessageContract&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;specification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jackson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;yourObjectMapper&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OrderPlaced&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;whenDeserializedAs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;OrderPlacedWithCoupon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenBackwardCompatible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What would you say if you saw such a test? Think about it, we’ll get back to it.&lt;/p&gt;
&lt;h2 id=&quot;whats-strictland&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#whats-strictland&quot; aria-label=&quot;whats strictland permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What’s Strictland?&lt;/h2&gt;
&lt;p&gt;But again, why did I do it if there are mature solutions like &lt;a href=&quot;https://pact.io/&quot;&gt;Pact&lt;/a&gt;, &lt;a href=&quot;https://spring.io/projects/spring-cloud-contract&quot;&gt;Spring Cloud Contracts&lt;/a&gt;, or &lt;a href=&quot;https://github.com/confluentinc/schema-registry&quot;&gt;Confluent Schema Registry&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;If you’ve used consumer-driven contract testing, the usual approach is to run both the provider and the consumer, record the consumer’s expectations against a mock, verify the provider against those expectations, and share those contracts through a broker.&lt;/p&gt;
&lt;p&gt;In Strictland, I took a smaller, simpler approach. It serialises the message in a standard unit test and saves the output as a snapshot file that you commit alongside your code.&lt;/p&gt;
&lt;p&gt;The test fails when the serialised shape changes, or when it contains a breaking change - up to you to specify expectations. A check confirms that an older and a newer version of the message can still read each other’s data (or the other way round).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Because it’s only serialisation and a file, the setup stays small:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The checks are ordinary unit tests in your existing suite, so there’s no broker, schema registry, or mock service to run, and nothing to start in Docker.&lt;/li&gt;
&lt;li&gt;The contract is the serialised JSON committed alongside the test, so any format change appears in a normal diff and is reviewed like any other code.&lt;/li&gt;
&lt;li&gt;You write the check beside the message it covers and get the answer in the same fast feedback loop as the rest of your tests. The check uses your application’s own serializer, so the snapshot is the exact bytes you ship.&lt;/li&gt;
&lt;li&gt;Strictland checks the serialised shape of a message and whether its versions stay compatible. It doesn’t exercise a live exchange between running services, so it complements that kind of tooling rather than replacing it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Strictland checks the serialised shape of a message and whether its versions stay compatible.&lt;/strong&gt; It doesn’t exercise a live exchange between running services, so it complements that kind of tooling rather than replacing it.&lt;/p&gt;
&lt;p&gt;It’s not as powerful as popular tooling, but it’s also much simpler to start catching our mistakes. Traditional solutions allow you to mock protocols, put a man in the middle, and even generate client code. All of that is great if you need it and have experience with it.&lt;/p&gt;
&lt;p&gt;Most of the &lt;a href=&quot;/en/training/&quot;&gt;customers I’m helping through consultancy and training&lt;/a&gt; aren’t there yet. Setting up those tools is a lot of heavy lifting for them and adds additional complexity. And well, maybe they don’t need to be, as this approach served me well in my past projects. I always handcrafted such a tool in my projects, but finally decided to make it properly.&lt;/p&gt;
&lt;h2 id=&quot;why-such-a-name&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-such-a-name&quot; aria-label=&quot;why such a name permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why such a name?&lt;/h2&gt;
&lt;p&gt;I’m putting (too?) much effort into my projects’ names. It’s a word game. Contract testing rewards a strict approach to your message shapes, and &lt;a href=&quot;https://backtothefuture.fandom.com/wiki/Stanford_S._Strickland&quot;&gt;Mr. Strickland&lt;/a&gt; was strict enforcer in &lt;em&gt;Back to the Future&lt;/em&gt;. That puts it in good company next to its sibling &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;, named after Doc Emmett Brown. I also didn’t want to collide with the older &lt;a href=&quot;https://github.com/jeffhandley/strickland&quot;&gt;JS validation package&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-to-use-it&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-to-use-it&quot; aria-label=&quot;how to use it permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How to use it?&lt;/h2&gt;
&lt;p&gt;Getting back to the essence… How to use it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You write a small unit test that locks down a message’s format.&lt;/strong&gt; Later, you rename a field, change a type, or adjust how a value serialises; the code still compiles and your other tests pass, but that one fails and points at what changed. You fix it in your build before a consumer or a stored event has hit the old format in production.&lt;/p&gt;
&lt;p&gt;When a message changes by accident, a snapshot check shows you exactly what moved. When you evolve a message on purpose, a compatibility check confirms an old and a new version can still read each other’s data.&lt;/p&gt;
&lt;p&gt;You can start by installing it from Maven Central, by adding dependency.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;xml&quot;&gt;&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;io.event-driven&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;groupId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;strictland&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;artifactId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;0.3.0&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;test&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;scope&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;dependency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then adding such test:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token class-name&quot;&gt;MessageContract&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;specification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jackson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OrderPlaced&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; placedAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;whenSerialized&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenContractIsUnchanged&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first run serializes the message and writes the result to an approved file named after the class, &lt;code class=&quot;language-text&quot;&gt;OrderPlaced.approved.txt&lt;/code&gt;, saved next to the test:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;orderId&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;00000000-0000-0000-0000-000000000001&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;customer&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;&quot;placedAt&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;2024-01-01T12:00:00Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You review that file and commit it. From then on the check compares against it and fails if the format drifts, so a later change to the format shows up in the same pull request as the code that caused it.&lt;/p&gt;
&lt;p&gt;A message under contract goes through one of two checks.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;snapshot check&lt;/strong&gt; confirms the message still serializes exactly as it did when you last approved it, so nothing reading it downstream breaks. A failure means the format changed: a field renamed, a date format switched, a value newly dropped or added.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;compatibility check&lt;/strong&gt; is for the version you evolve on purpose, so changing a message doesn’t leave the ones already in your store or on the wire stranded. Use &lt;code class=&quot;language-text&quot;&gt;thenBackwardCompatible()&lt;/code&gt; to confirm the newer version still reads a message the older one wrote, the events you stored last year, or a request already sent. Use &lt;code class=&quot;language-text&quot;&gt;thenForwardCompatible()&lt;/code&gt; to confirm a reader that hasn’t upgraded yet still reads a message the newer version writes, so you can ship the new shape before everyone reading it has caught up. Both compare the fields the two versions share and fail if a required one is missing or a shared value has changed.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token annotation punctuation&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ensureOrderPlacedCompatibilityWithNewerVersion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Strictland specification&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;MessageContract&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;specification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jackson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;yourObjectMapper&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OrderPlaced&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; “&lt;span class=&quot;token class-name&quot;&gt;Alice&lt;/span&gt;”&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;whenDeserializedAs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;OrderPlacedWithCoupon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thenBackwardCompatible&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You review that file and commit it. From then on the check compares against it and fails if the format drifts, so a later change to the format shows up in the same pull request as the code that caused it.&lt;/p&gt;
&lt;p&gt;As you see, in the specification, you pass your serializer.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;java&quot;&gt;&lt;pre class=&quot;language-java&quot;&gt;&lt;code class=&quot;language-java&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; spec &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MessageContract&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;specification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Jackson&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;yourObjectMapper&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I encourage you to use your application’s object mapper and pass the same &lt;code class=&quot;language-text&quot;&gt;ObjectMapper&lt;/code&gt; it uses, so the test checks the exact bytes you ship. That’ll help you avoid nasty surprises, where tests are using a different format than your framework or tooling.&lt;/p&gt;
&lt;p&gt;Strictland provides an implementation of a sensible Jackson setup: ISO-8601 dates, nulls kept, unknown properties ignored on read. You can use it with &lt;code class=&quot;language-text&quot;&gt;Json.Jackson.defaults()&lt;/code&gt;. But it’s more of an accessible thing to get you started quickly.&lt;/p&gt;
&lt;p&gt;You can also define your own serializer if you’re using an unsupported (yet?) format or serializer type. See basic examples in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/event-driven-io/strictland/blob/main/src/jvm/src/test/java/io/eventdriven/strictland/CsvMessageSerializer.java&quot;&gt;CsvMessageSerializer&lt;/a&gt; and its &lt;a href=&quot;https://github.com/event-driven-io/strictland/blob/main/src/jvm/src/test/java/io/eventdriven/strictland/CsvMessageSerializerTests.java&quot;&gt;tests&lt;/a&gt; or,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/event-driven-io/strictland/blob/main/src/jvm/src/test/java/io/eventdriven/strictland/SimpleBinaryMessageSerializer.java&quot;&gt;SimpleBinaryMessageSerializer&lt;/a&gt; and its &lt;a href=&quot;https://github.com/event-driven-io/strictland/blob/main/src/jvm/src/test/java/io/eventdriven/strictland/SimpleBinaryMessageSerializerTests.java&quot;&gt;tests&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;should-you-use-it&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#should-you-use-it&quot; aria-label=&quot;should you use it permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Should you use it?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/0a5d1001eec464f7501bcf275c1b73d5/2026-06-15-yes-you-do.gif&quot; alt=&quot;Yes, you do!&quot;&gt;&lt;/p&gt;
&lt;p&gt;Strictland is young and pre-1.0, so the API can still move between versions. The checks themselves are small and well covered, and the snapshots they produce are just files in your repository, so trying it out costs little and commits you to nothing.&lt;/p&gt;
&lt;p&gt;I’d genuinely like your feedback. If something is missing or awkward, tell me through comments, on &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Discord&lt;/a&gt; or open an &lt;a href=&quot;https://github.com/event-driven-io/strictland/issues/new&quot;&gt;issue&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Currently, I’m working on making snapshot location more organised and configurable, not to end up with a bloated snapshots tests folder.&lt;/p&gt;
&lt;p&gt;I also need to rethink the &lt;em&gt;given&lt;/em&gt; name; it’s from Behaviour-Driven Design, but it appears to be a &lt;a href=&quot;https://docs.scala-lang.org/scala3/reference/contextual/givens.html&quot;&gt;reserved word in Scala 3&lt;/a&gt;. Cross-language and cross-stack libs are much fun!&lt;/p&gt;
&lt;p&gt;For now, for JVM, as the Java-based customers I’m helping also needed that, but soon want to add .NET and TypeScript/Javascript (and maybe more).&lt;/p&gt;
&lt;p&gt;Tell me your honest thoughts, give it a try and a star if you liked it. Play with it, and pass me your feedback. It’ll motivate me to continue working on it!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Would you like to learn more about consumer-driven tests and how Strictland can be used for them?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And read in the event versioning series:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/simple_events_versioning_patterns/&quot;&gt;Simple patterns for events schema versioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/explicit_events_serialisation_in_event_sourcing/&quot;&gt;Explicit events serialisation in Event Sourcing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/fun_with_json_serialisation/&quot;&gt;Fun with serial JSON&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_map_event_type_by_convention/&quot;&gt;Mapping event type by convention&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/event_versioning_with_marten/&quot;&gt;Event Versioning with Marten&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/lets_take_care_of_ourselves_thoughts_about_comptibility/&quot;&gt;Let’s take care of ourselves! Thoughts on compatibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/internal_external_events/&quot;&gt;Internal and external events, or how to design event-driven API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[You can fork a package, but can you own it?]]></title><description><![CDATA[Fork your dependencies, trim them to only your use case, never update unless it breaks for your users. I’ve been vocal about this for 1…]]></description><link>https://event-driven.io/en/you-can-fork-a-package-but-can-you-own-it/</link><guid isPermaLink="false">https://event-driven.io/en/you-can-fork-a-package-but-can-you-own-it/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e6c27458745f718fad8a8b622583ec26/8fe94/2026-06-08-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEAgX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAP/aAAwDAQACEAMQAAAB2/lsSojI/8QAGxAAAwACAwAAAAAAAAAAAAAAAAECAwQRFDH/2gAIAQEAAQUCxZ6hPa4O4xHjqT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQIBAT8BiY//xAAZEAACAwEAAAAAAAAAAAAAAAAAARARMYH/2gAIAQEABj8CpmGI4OP/xAAbEAADAQEAAwAAAAAAAAAAAAAAAREhMVFxgf/aAAgBAQABPyHqDaT2mdIr6JK7uqXyVLom5EvR/9oADAMBAAIAAwAAABBTP//EABYRAAMAAAAAAAAAAAAAAAAAAAABEf/aAAgBAwEBPxCsrP/EABYRAQEBAAAAAAAAAAAAAAAAAAEQEf/aAAgBAgEBPxBGw//EABwQAQEBAQACAwAAAAAAAAAAAAERIQBRoWGBsf/aAAgBAQABPxDMZRVFnjlbV0ECfFvBugbG/wB5lZObNcPo5Y2EJFGmnr30VaCyWd//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/e6c27458745f718fad8a8b622583ec26/507e6/2026-06-08-cover.webp 200w,
/static/e6c27458745f718fad8a8b622583ec26/28a80/2026-06-08-cover.webp 400w,
/static/e6c27458745f718fad8a8b622583ec26/8d2ea/2026-06-08-cover.webp 800w,
/static/e6c27458745f718fad8a8b622583ec26/68fc1/2026-06-08-cover.webp 1200w,
/static/e6c27458745f718fad8a8b622583ec26/43d96/2026-06-08-cover.webp 1600w,
/static/e6c27458745f718fad8a8b622583ec26/7c4f2/2026-06-08-cover.webp 2048w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/e6c27458745f718fad8a8b622583ec26/37402/2026-06-08-cover.jpg 200w,
/static/e6c27458745f718fad8a8b622583ec26/4cda9/2026-06-08-cover.jpg 400w,
/static/e6c27458745f718fad8a8b622583ec26/c60e9/2026-06-08-cover.jpg 800w,
/static/e6c27458745f718fad8a8b622583ec26/6c738/2026-06-08-cover.jpg 1200w,
/static/e6c27458745f718fad8a8b622583ec26/56dca/2026-06-08-cover.jpg 1600w,
/static/e6c27458745f718fad8a8b622583ec26/8fe94/2026-06-08-cover.jpg 2048w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/e6c27458745f718fad8a8b622583ec26/c60e9/2026-06-08-cover.jpg&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Fork your dependencies, trim them to only your use case, never update unless it breaks for your users. I’ve been vocal about this for 10+ years. I’ve always said that updating is way riskier than latent bugs (which can be tracked and CVEs monitored).&lt;/p&gt;
&lt;p&gt;If you are updating a dependency, it’s on you to analyze every single commit in the full transitive set of dependencies. If you dont see anything compelling, dont update!&lt;/p&gt;
&lt;p&gt;I remember at HashiCorp once in awhile an engineer would try to update a dep or replace a DIY lib with an external one and id always ask “show me the commit we need.” Dont update for the sake of it.&lt;/p&gt;
&lt;p&gt;Feeling pretty swell about this mentality with all the supply chain attacks happening.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://x.com/mitchellh/status/2057171518027887035&quot;&gt;That’s from Mitchell Hashimoto&lt;/a&gt;. A friend sent it to me, and the first word he reached for was &lt;em&gt;bold&lt;/em&gt;.&lt;/strong&gt; Mine too. I mostly agree with Mitchell, honestly. But the second I read it, my head jumped to a caveat, and the caveat turned out to be the thing I actually wanted to write about.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We can fork a small library and trim it to what we use. I’ve done it, it’s fine. But could we fork React and maintain it?&lt;/strong&gt; I couldn’t. I don’t think most teams could either. So “fork your dependencies” is wonderful advice right up to the point where the dependency is too big to hold in our hands, and then it quietly stops being advice at all.&lt;/p&gt;
&lt;p&gt;And that caveat says something about the advice itself. I think that it is, actually, not about forking. I see it more about knowing exactly what we’ve taken on and being willing to own it, and that’s the bit most of us skip. We don’t &lt;em&gt;decide&lt;/em&gt; to take a dependency. We just install one. And almost everything that goes wrong with dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the supply chain attacks,&lt;/li&gt;
&lt;li&gt;the licence dramas,&lt;/li&gt;
&lt;li&gt;the half-finished SBOMs,&lt;/li&gt;
&lt;li&gt;the things we find out about only after they break&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;comes back to that one move: we took dependence on someone else’s product without ever deciding to. Everything else here is a variation on it.&lt;/p&gt;
&lt;h2 id=&quot;to-fork-or-not&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#to-fork-or-not&quot; aria-label=&quot;to fork or not permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;To fork or not?&lt;/h2&gt;
&lt;p&gt;I was reminded of this some time ago on a &lt;a href=&quot;https://www.youtube.com/watch?v=Cf2oU_SUm3Y&quot;&gt;live Q&amp;#x26;A about my TypeScript port of some code&lt;/a&gt;, and a whole chunk of it was one question asked five different ways:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Why did you write your own &lt;code class=&quot;language-text&quot;&gt;deepEquals&lt;/code&gt; instead of just pulling a &lt;a href=&quot;https://www.npmjs.com/package/deep-equal&quot;&gt;package&lt;/a&gt;?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I tried to explain that it wasn’t overachieving, and it wasn’t not-invented-here pride either. I explained that maybe the code wasn’t trivial, but straightforward enough for me, and is the type of code I write once and forget. The reception was, let’s say, mixed. I get it, it’s much easier to just install a package and outsource maintenance costs. Especially when we’re in a hurry, we don’t make any decisions; we just do. We reach. And this reach can be a decent interim solution, but we should reflect on our next steps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why do I reach for my own code on the small stuff?&lt;/strong&gt; Look at which packages actually get hit in all these supply chain attacks we keep reading about. It’s almost always the little ones. The one-liners. Remember the &lt;a href=&quot;https://en.wikipedia.org/wiki/Npm_left-pad_incident&quot;&gt;Left-pad Incident&lt;/a&gt;? It’s usually some tiny or low-level helper nobody thinks about. They’re everywhere, they’re trivial, and precisely because they’re trivial, nobody is watching them. Which is also exactly why they’re so easy to write ourselves. So for me, handwriting a small helper doesn’t always mean that we’re paranoid. It may be an option that will buy us some calm. It gives us knowledge of exactly what is in our own system.&lt;/p&gt;
&lt;p&gt;That’s also why, in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett, my Event Sourcing library,&lt;/a&gt;, I’m stubborn about keeping the core package free of dependencies and limiting whatever I inject elsewhere. Probably too stubborn, if I’m honest. But I’d rather err on the side of caution, because when it goes wrong on the user’s side, it goes far worse than a bit of code I maintain myself. It’s about responsibility for the outcome of our actions.&lt;/p&gt;
&lt;p&gt;If I had to compress what I believe into one line, it wouldn’t be “fork everything”, and it wouldn’t be “write everything ourselves”. It would be something much less sexy than those two:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Be precise about which dependencies we take on, look at how many dependencies pull in, and treat that as part of the decision. They’re now our dependencies.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The number of dependencies itself isn’t the point. It just tells us how much we’re agreeing to own without ever seeing it.&lt;/p&gt;
&lt;p&gt;Because our direct dependencies were never the worst part, we chose those more or less cautiously. Most of the time, we can follow what they’re doing. The scary part is the dependencies of our dependencies, and theirs, all the way down, the part we didn’t choose, can’t see, and have no say over.&lt;/p&gt;
&lt;p&gt;And I want to be careful here, because it’s easy to let this slide into another round of JavaScript-bashing, and that’s not what I mean. Every ecosystem has this. JS and TypeScript just sit at one far end of it, where there’s a package for absolutely everything, which is, in general, good, as we don’t need to reinvent the wheel every other week, unlike in some other places. But it’s also how we end up with a node_modules we couldn’t fully explain if someone put a gun to our head.&lt;/p&gt;
&lt;p&gt;At the other extreme, there’s Microsoft and .NET, where the instinct runs so hard the opposite way that it tips into &lt;a href=&quot;https://en.wikipedia.org/wiki/Not_invented_here&quot;&gt;Not Invented Here Syndrome&lt;/a&gt;. Neither end is the “right” one. They’re both defaults people drift into without ever making a decision.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For me, it’s not about reaching zero dependencies. But having dependencies that we cautiously agreed upon.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Which takes me to the part that, in my experience, almost nobody does. We can’t make a call on what we can’t see, and if we don’t even have the basic knowledge (e.g. some list) of what we depend on, then every conversation about supply chain risk is a bit of theatre.&lt;/p&gt;
&lt;h2 id=&quot;dependency-inventory&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dependency-inventory&quot; aria-label=&quot;dependency inventory permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dependency Inventory&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;In most of the environments, &lt;a href=&quot;https://openssf.org/technical-initiatives/sbom-tools/&quot;&gt;there are tools&lt;/a&gt; to generate the &lt;a href=&quot;https://github.com/resources/articles/what-is-an-sbom-software-bill-of-materials&quot;&gt;Software Bill of Materials&lt;/a&gt; - the inventory of our dependency tree.&lt;/strong&gt; In some, they’re even built in. It’s easy to dunk on NPM, but it’d be better to do due diligence before doing so. Not many people seem to know this, but recent versions of npm ship an &lt;a href=&quot;https://docs.npmjs.com/cli/v11/commands/npm-sbom&quot;&gt;npm sbom&lt;/a&gt;. So the tooling exists, even in NPM. That isn’t the problem.&lt;/p&gt;
&lt;p&gt;The problem is that most organisations have never generated one in their life. No SBOM, no inventory, nothing written down anywhere. So the day the next &lt;a href=&quot;https://en.wikipedia.org/wiki/Log4Shell&quot;&gt;Log4Shell&lt;/a&gt; lands, and there will be a next one, they can’t answer the very first question anyone will ask them: do we run this, and if so, where?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On the other hand, tools often don’t help here, even those built to do so.&lt;/strong&gt; &lt;a href=&quot;https://overreacted.io/npm-audit-broken-by-design/&quot;&gt;NPM audit mostly does the opposite&lt;/a&gt;. I honestly can’t remember the last time I installed something, and the audit didn’t immediately tell me to bump a stack of packages. Most of it is false positives, with no real attempt to say how dangerous any of it is. And that lands us in the oldest trap going: if it’s always red, we stop looking at red. So the one signal that was supposed to make us stop and decide ends up training everyone to decide nothing.&lt;/p&gt;
&lt;h2 id=&quot;bus-factor-and-rug-pulls&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#bus-factor-and-rug-pulls&quot; aria-label=&quot;bus factor and rug pulls permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Bus factor and rug pulls&lt;/h2&gt;
&lt;p&gt;There’s a related thing I can’t quite leave alone, so let me wander into it. I think a lot of teams spend their energy on the symptom and never once look at the source.&lt;/p&gt;
&lt;p&gt;Watch what happens in the .NET world whenever a popular package changes the deal. &lt;a href=&quot;https://www.infoq.com/news/2025/01/fluent-assertions-v8-license/&quot;&gt;Fluent Assertions&lt;/a&gt; went commercial. &lt;a href=&quot;https://snyk.io/blog/moq-package-exfiltrates-user-emails/&quot;&gt;Moq shipped a thing that quietly hashed git email and phoned it home&lt;/a&gt;. MassTransit and &lt;a href=&quot;https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/&quot;&gt;AutoMapper&lt;/a&gt; announced commercial licenses within the same stretch. And nearly every time, the reaction across .NET shops is identical. It’s a mixture of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;let’s rip it out and write their own,&lt;/li&gt;
&lt;li&gt;search for a free alternative,&lt;/li&gt;
&lt;li&gt;Cry to Microsoft to buy the lib or provide a replacement,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Essentially: a throw-the-baby-out-with-the-bathwater strategy.&lt;/p&gt;
&lt;p&gt;And for me, that’s solving the wrong thing entirely. The source isn’t that the package started charging money, or pulled a rug. &lt;strong&gt;The source is that we took on a critical dependency without ever admitting to ourselves that it was critical, and never once thought about what we’d do if the terms changed.&lt;/strong&gt; We didn’t consider the bus factor, and we didn’t do due diligence to ensure the work on it was sustainable and could continue. Pulling it out and hand-rolling a replacement fixes none of that. It just resets the same trap, this time with only the code we maintain.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.identityserver.com/articles/identityserver-vnext-duende-identityserver&quot;&gt;The IdentityServer episode&lt;/a&gt; was the clearest version of it I’ve seen. People were upset that they had to pay suddenly. Then, in the next sentence, it is called a critical security component. Then, in the sentence after that, it asks what the free alternatives were. A critical security component that we want for free and are ready to swap out overnight is, to my mind, asking for a security incident.&lt;/p&gt;
&lt;p&gt;And there’s a bit of maths that quietly settles most of these arguments, if anyone bothers to do it. Take what the licence costs us per year. Then take into account what it would cost to have an engineer build and maintain our own version. Put the two side by side.&lt;/p&gt;
&lt;p&gt;Almost every time, paying the maintainer comes out cheaper, and on top of that, we’ve lowered the bus factor on something we already lean on, which is its own kind of supply chain security. “We’d write it ourselves, but then we’d have to maintain it” is true. I just read it as the argument for paying the person who already does, not against it. If we depend on something, its survival is our problem too. That’s part of owning the decision.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/en/why-open-source-isnt-always-fair/&quot;&gt;I know this case too well&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;llm-as-a-fork&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#llm-as-a-fork&quot; aria-label=&quot;llm as a fork permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;LLM as a fork&lt;/h2&gt;
&lt;p&gt;Getting back to Mitchell’s thought. The part I find most interesting is because of the moment we’re in. I keep hearing that LLMs change all of this, that writing our own small things is suddenly trivial, so the whole dependency question softens. I don’t buy it. It’s never that easy. Writing the small thing was never the hard part anyway. Owning it, understanding it, maintaining it, being the one on the hook when it breaks at 2 am, that’s the hard part, and no model takes that off our plate.&lt;/p&gt;
&lt;p&gt;I don’t see how LLMs can change the cost of owning code. They can (&lt;a href=&quot;/en/the_end_of_coding_wrong_question/&quot;&gt;maybe&lt;/a&gt;) change the cost of producing it. That doesn’t fix the “install without deciding”. The old move was install and move on. The new move is “vibe it” and move on. Same missing decision, new flavour. The same lack of responsibility and ownership.&lt;/p&gt;
&lt;p&gt;This trend isn’t new. It’s a classic &lt;a href=&quot;https://en.wikipedia.org/wiki/Shadow_IT&quot;&gt;Shadow IT&lt;/a&gt;. If you haven’t been around long enough to run into the term, Shadow IT refers to the tools and systems people build or adopt within a company without going through whoever is officially meant to approve them. The spreadsheet that quietly runs a whole department. The little script someone wrote on a Friday that half the team now depends on. Nobody in the platform group has ever heard of the integration. It has always existed because people route around slow governance to get their job done, and most of the time, nobody notices until it breaks.&lt;/p&gt;
&lt;p&gt;With LLMs, it’s more tempting than it has ever been. Someone in sales promises a customer a feature the team supposedly needs. The team has no time, so they cobble a tool together from the API and ship it. It doesn’t work. The customer says they’re not paying for this. It escalates. The thing was unowned from the moment it was conceived; nobody decided to take it on, it just appeared, and the blame game is starting.&lt;/p&gt;
&lt;p&gt;And here’s where I think it all settles, because the corporate steamroller flattens everything in the end. Companies will dictate the allowed list, the way they always have. The cautious majority will stick to what’s known and popular: React, TypeScript, Python, Spring Boot. That’s what they did last time, and the time before. And the people who want to move faster will do it off the books, with an LLM, as Shadow IT. The declarative, standards-based frameworks that hide their complexity will do well in that world, because that style suits how these tools work, but it’s the same ceiling as before. We bet on React. We don’t own it. The small stuff we can hold; the big stuff stays as bet.&lt;/p&gt;
&lt;h2 id=&quot;what-to-do-then&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-to-do-then&quot; aria-label=&quot;what to do then permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What to do then?&lt;/h2&gt;
&lt;p&gt;We cannot fix the entire software industry, but we can fix how our own engineering teams operate. Instead of waiting for automated audits to scream at us, or ripping out packages in an emotional panic, I suggest a simple, regular exercise for our organisation.&lt;/p&gt;
&lt;p&gt;Sit down and explicitly define your dependency posture:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Inventory:&lt;/strong&gt; List the dependencies you use (even without peer dependencies). Use tools &lt;code class=&quot;language-text&quot;&gt;npm-sbom&lt;/code&gt; to actually see what you are pulling in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Criticality:&lt;/strong&gt; Identify which of these packages are absolutely critical to your system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lifecycle:&lt;/strong&gt; Define a clear strategy for upgrading and versioning them. Are you updating just for the sake of it, or are you looking for specific commits like Mitchell suggests?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Bus Factor:&lt;/strong&gt; Ask yourself: what happens if the author of a critical package gets hit by a bus, burns out, or the tool becomes paid?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mitigation:&lt;/strong&gt; Decide on a concrete backup plan for that exact scenario. Do you fork it? Do you pay the license fee? Maybe pay earlier for support or help in another way to maintain it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response Time:&lt;/strong&gt; Estimate how quickly you can upgrade and deploy the application if a major security breach occurs in a dependency. Also, if the strategy is to use replacement, then how fast will you be able to replace this dependency?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Building reliable software requires intent. We don’t have to write everything from scratch, but we must be precise about what we bring into our software. Architecture is not just about writing code; it is about choosing which liabilities we are willing to own.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How soon is now in PostgreSQL?]]></title><description><![CDATA[How soon is now? In PostgreSQL, it’s not always as soon as you’d think. I learned that the hard way recently, so you don’t have to. It took…]]></description><link>https://event-driven.io/en/how-soon-is-now-in-postgresql/</link><guid isPermaLink="false">https://event-driven.io/en/how-soon-is-now-in-postgresql/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/38bff92c4f54acc9e02aab58e8a3d196/7032b/2026-05-25-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAADIElEQVR42hWS20+bBRiHv3svvDbxwiv1Qndh4mYc2SZR0c0Bq+AcVogSTj1AHaylHEdBYDDkUE4d41xgrLQUWkppP+Dr17K6llJOBTYIi85lRm/M/gAf6+WbvHl+v7zvI4jmn1ju0jF9V4+luZLOxmqq9QbKy400V5WzaLqBrTabycpMxm+lM1qRQb86jX7Vp1QoPiTn2+u4PT7WpBCuJR/CqElFelkPn5cNo9SYMep7sFqXydTPUHa7A7GvFIf5FqPtaqbbSnENVDJZp6RPk0peRipzCx4ST455aHfi9a0jjNQXclU3RNQfYv/BQw4nJvj3xQn3xrwU6X/G2V7IZGMhs60q7tXlM1T7A726dO6XXEKbr2Rr95Do1g7DI+PEtxMIU3V5pKl6iUthns/PEhubJDg9R23FXd5V3mGm+jrx+fscxSPshiU2QxIzllYa8lMp+E7BqvyYg6MT9vafIAUeIYzpszinbEKt68ZQY+GLol7eVnZwWd2OtraLZm0OYUnk9OU/bCdOSJz8gRxYZ2q4i5Lcr2mqryISi7EmyzidiwiOlgK83gVsNhsu0Y9tJczsWgT70iqPIlFab/6Iyz7H02cv2Nl/yuPoNgtzVkTPDB1NVUz1mAisWHHbB+g03URwthfz6q9tfj+W+fM0yKu/X/Lb6XMWXUs8mPfwi7GEIZOOaHgDSZaIbu4gJpt03Wmm1dRANLqLdXyCwe5O8r5KQRjUKVjuN2GuKmKwUcWWbRjZu4LPL+Gwu6hXKalUXuHZ0TEHh8fEt/YQJTkJirPu9SJLGwSSuwExQFNxFoJFk8lygxZzvgJ54Da+Jg2WegOB5LE3f43gmU4GiGvI6xscJA4JJWF+OZz0boNYbI+gmJyT6oT+BxZkIBiyU9BlnMdhKmbH2kZr3hXS3n+LsqxPKMhIwVyjIZx8wtryEq7xQRbH+vC7XbgddsLBEJuyj+DKHPGQmzb1NYSq7I8oz77AarceoyKFD958nTNvvMb3F99B9eUZjN98jFl1mZbci2g/e4+arLPU5FxAffUsLUXpzHTqGWkppcuQi+HGJf4DwKNg4dSR+BsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/38bff92c4f54acc9e02aab58e8a3d196/507e6/2026-05-25-cover.webp 200w,
/static/38bff92c4f54acc9e02aab58e8a3d196/28a80/2026-05-25-cover.webp 400w,
/static/38bff92c4f54acc9e02aab58e8a3d196/8d2ea/2026-05-25-cover.webp 800w,
/static/38bff92c4f54acc9e02aab58e8a3d196/68fc1/2026-05-25-cover.webp 1200w,
/static/38bff92c4f54acc9e02aab58e8a3d196/5aa59/2026-05-25-cover.webp 1408w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/38bff92c4f54acc9e02aab58e8a3d196/36ca5/2026-05-25-cover.png 200w,
/static/38bff92c4f54acc9e02aab58e8a3d196/a3397/2026-05-25-cover.png 400w,
/static/38bff92c4f54acc9e02aab58e8a3d196/a331c/2026-05-25-cover.png 800w,
/static/38bff92c4f54acc9e02aab58e8a3d196/8537d/2026-05-25-cover.png 1200w,
/static/38bff92c4f54acc9e02aab58e8a3d196/7032b/2026-05-25-cover.png 1408w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/38bff92c4f54acc9e02aab58e8a3d196/a331c/2026-05-25-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How soon is now? In PostgreSQL, it’s not always as soon as you’d think.&lt;/strong&gt; I learned that the hard way recently, so you don’t have to.&lt;/p&gt;
&lt;p&gt;It took me hours and wasn’t easy to reproduce, even though the fix is one line. I found it in &lt;a href=&quot;https://www.cybertec-postgresql.com/en/postgresql-now-vs-nowtimestamp-vs-clock_timestamp/&quot;&gt;a Cybertec post&lt;/a&gt;, as I quite often do when I’m staring at something odd in PostgreSQL. I’m supposed to know my way around the database, but I missed it, which is another reason I want to write this down.&lt;/p&gt;
&lt;p&gt;I was working on distributed locking in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;. When you scale a service horizontally, you can easily end up with two instances of the same message processor running at once. That’s bad. Both instances would pull the same events, both would write to the same projection storage, and we’d get duplicated side effects, overwritten state and broken checkpoints. So we need to guarantee that exactly one instance of each processor is active at any time. Emmett does that using two things working hand in glove: PostgreSQL advisory locks and a row in the &lt;code class=&quot;language-text&quot;&gt;emt_processors&lt;/code&gt; table. The row keeps the durable side of ownership: which instance currently holds the processor (&lt;code class=&quot;language-text&quot;&gt;processor_instance_id&lt;/code&gt;), when it last checked in (&lt;code class=&quot;language-text&quot;&gt;last_updated&lt;/code&gt;), and what state it’s in (&lt;code class=&quot;language-text&quot;&gt;status&lt;/code&gt;). I described the full design in &lt;a href=&quot;/en/rebuilding_event_driven_read_models/&quot;&gt;Rebuilding Event-Driven Read Models in a safe and resilient way&lt;/a&gt;, so I won’t bore you with the whole picture here.&lt;/p&gt;
&lt;p&gt;For this story, the part that matters is what happens when an instance crashes. The crashed processor’s connection is gone, so its advisory lock has already been released. A new instance can grab the advisory lock without resistance. But the row in &lt;code class=&quot;language-text&quot;&gt;emt_processors&lt;/code&gt; still says &lt;code class=&quot;language-text&quot;&gt;status = &apos;running&apos;&lt;/code&gt; and still points to the previous owner, because the crash didn’t give anyone a chance to clean it up.&lt;/p&gt;
&lt;p&gt;From the outside, we can’t tell whether the previous owner has crashed or is just between heartbeats. So we wait. If the row’s &lt;code class=&quot;language-text&quot;&gt;last_updated&lt;/code&gt; is older than a configurable timeout, the new instance is allowed to claim ownership anyway. Anyone quiet for that long is treated as gone. To make this graceful, the lock acquisition runs inside a retry policy. A fresh instance starting just after a crash doesn’t fail straight away; it retries until the timeout window expires.&lt;/p&gt;
&lt;h2 id=&quot;the-bug&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-bug&quot; aria-label=&quot;the bug permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The bug&lt;/h2&gt;
&lt;p&gt;The takeover decision lives in the upsert against &lt;code class=&quot;language-text&quot;&gt;emt_processors&lt;/code&gt;. In the real function, that upsert sits inside a Common Table Expression (CTE) alongside a &lt;code class=&quot;language-text&quot;&gt;pg_try_advisory_xact_lock&lt;/code&gt; call.&lt;/p&gt;
&lt;p&gt;For the record: the snippets below skip that wrapping (and trim a couple of unused parameters) to keep the focus on the upsert, where the bug lives. The full version is in &lt;a href=&quot;https://github.com/event-driven-io/emmett/blob/4c5909982313654f7df383a44b02a14a04f30b50/src/packages/emmett-postgresql/src/eventStore/schema/processors/processorsLocks.ts&quot;&gt;the source&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; emt_try_acquire_processor_lock&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    p_processor_id           &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    p_processor_instance_id  &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    p_lock_timeout_seconds   &lt;span class=&quot;token keyword&quot;&gt;INT&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;RETURNS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BOOLEAN&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;LANGUAGE&lt;/span&gt; plpgsql
&lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; $$
&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; emt_processors &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processor_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; processor_instance_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; last_updated&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p_processor_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p_processor_instance_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;running&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; CONFLICT &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processor_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DO&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; processor_instance_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_processor_instance_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;                &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;running&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      last_updated          &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt;   
     &lt;span class=&quot;token comment&quot;&gt;-- same instance reconnecting&lt;/span&gt;
     emt_processors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processor_instance_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_processor_instance_id                      
     &lt;span class=&quot;token comment&quot;&gt;-- previous owner stopped cleanly&lt;/span&gt;
     &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; emt_processors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;stopped&apos;&lt;/span&gt;     
     &lt;span class=&quot;token comment&quot;&gt;-- previous owner timed out    &lt;/span&gt;
     &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; emt_processors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;last_updated
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p_lock_timeout_seconds &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos; seconds&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; FOUND&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
$$&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The last branch is the takeover. It reads naturally: if the previous owner hasn’t checked in for longer than the timeout, the new instance can replace them. All tests were green. Stop me if you think you’ve heard this one before. The problem surfaced through user feedback (thanks, &lt;a href=&quot;https://www.linkedin.com/in/martindilger/&quot;&gt;Martin&lt;/a&gt;!), and it took me a long time to reproduce; none of the existing tests covered the scenario that triggered it. &lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/339/changes#diff-c790c5d796e8e155d3e621f9b5bc2843503eda7c121c7715c232dbe9608a8964R741&quot;&gt;Once I had a new end-to-end test that pinpointed the symptom&lt;/a&gt;, the rest was the usual, long, boring, debugging loop.&lt;/p&gt;
&lt;p&gt;To see why, open &lt;code class=&quot;language-text&quot;&gt;psql&lt;/code&gt; and run this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; tx_now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; clock_timestamp&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; wall_clock&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pg_sleep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; tx_now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; clock_timestamp&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; wall_clock&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;COMMIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You’ll get something like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;            tx_now             |          wall_clock
-------------------------------+-------------------------------
 2026-05-25 10:00:00.123456+00 | 2026-05-25 10:00:00.124012+00

            tx_now             |          wall_clock
-------------------------------+-------------------------------
 2026-05-25 10:00:00.123456+00 | 2026-05-25 10:00:02.131845+00&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first column is the same in both rows. The second one is two seconds apart. As it turns out, &lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt; is a synonym for &lt;code class=&quot;language-text&quot;&gt;transaction_timestamp()&lt;/code&gt;: it returns the time the transaction began, and keeps returning that value for every statement inside the same transaction. A light that never goes out, in other words. &lt;code class=&quot;language-text&quot;&gt;clock_timestamp()&lt;/code&gt; reads the wall clock each time it’s called, so it advances as time does. Cybertec wrote &lt;a href=&quot;https://www.cybertec-postgresql.com/en/postgresql-now-vs-nowtimestamp-vs-clock_timestamp/&quot;&gt;a good walkthrough&lt;/a&gt; of the whole family of timestamp functions if you want the full picture.&lt;/p&gt;
&lt;p&gt;What difference does it make? For a column like &lt;code class=&quot;language-text&quot;&gt;last_updated&lt;/code&gt;, the constancy of &lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt; is usually what you want: every row touched in the same transaction shares a single timestamp, which keeps audit logs and write batches coherent. For asking “has enough time passed?” inside the same transaction, the same constancy works against us.&lt;/p&gt;
&lt;p&gt;Now back to the retry. The consumer that calls &lt;code class=&quot;language-text&quot;&gt;tryAcquire&lt;/code&gt; looks roughly like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;pool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;asyncRetry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tryAcquireProcessorLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; retries&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; minTimeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; maxTimeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;pool.withTransaction&lt;/code&gt; opens a database transaction and passes its executor to the body. &lt;code class=&quot;language-text&quot;&gt;asyncRetry&lt;/code&gt; then repeatedly calls the stored procedure on that same executor, with a backoff between attempts. So even though the retries are spread out in real time, every call runs inside the same database transaction:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;withTransaction        (transaction starts at T)
  └── asyncRetry
       ├── call lock function    → now() = T
       ├── call lock function    → now() = T   (200 ms later)
       ├── call lock function    → now() = T   (400 ms later)
       └── ...&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;last_updated &amp;lt; now() - timeout&lt;/code&gt; evaluates the same way every iteration. The predicate is effectively constant for the lifetime of that transaction. From the database’s perspective, no time was passing between attempts, even though the retries were spread across real seconds. (of course, the valid question is whether retries should happen inside a transaction, but let’s say that this is out of scope of today’s article, deal?).&lt;/p&gt;
&lt;p&gt;So what was the fix? Change the time source inside the function. PL/pgSQL lets you declare local variables, so I added one at the top, initialised from &lt;code class=&quot;language-text&quot;&gt;clock_timestamp()&lt;/code&gt;, and used it everywhere the function previously called &lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; emt_try_acquire_processor_lock&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    p_processor_id           &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    p_processor_instance_id  &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    p_lock_timeout_seconds   &lt;span class=&quot;token keyword&quot;&gt;INT&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;RETURNS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BOOLEAN&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;LANGUAGE&lt;/span&gt; plpgsql
&lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; $$
&lt;span class=&quot;token keyword&quot;&gt;DECLARE&lt;/span&gt;
  v_current_time TIMESTAMPTZ :&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; clock_timestamp&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; emt_processors &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processor_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; processor_instance_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; last_updated&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p_processor_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p_processor_instance_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;running&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; v_current_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; CONFLICT &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processor_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DO&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; processor_instance_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_processor_instance_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;                &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;running&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      last_updated          &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; v_current_time
  &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; emt_processors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processor_instance_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_processor_instance_id
     &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; emt_processors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;stopped&apos;&lt;/span&gt;
     &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; emt_processors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;last_updated
        &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; v_current_time &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p_lock_timeout_seconds &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos; seconds&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; FOUND&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
$$&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;clock_timestamp()&lt;/code&gt; ignores transaction boundaries. Every call to the stored procedure reads the wall clock fresh, so each retry sees a slightly later value than the last. After enough retries inside the wrapping transaction, the takeover predicate flips, and the new instance wins.&lt;/p&gt;
&lt;p&gt;The function uses the timestamp in two places: when setting &lt;code class=&quot;language-text&quot;&gt;last_updated&lt;/code&gt; on the new owner, and when comparing the previous owner’s &lt;code class=&quot;language-text&quot;&gt;last_updated&lt;/code&gt; to the timeout. I switched both to &lt;code class=&quot;language-text&quot;&gt;v_current_time&lt;/code&gt;, so the write and the check read from the same wall clock. Mixing &lt;code class=&quot;language-text&quot;&gt;clock_timestamp()&lt;/code&gt; on one side and &lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt; on the other would leave a subtler version of the same bug.&lt;/p&gt;
&lt;p&gt;A cleaner option for the future is to move the retry one layer up, so each attempt opens its own transaction. That would remove the trap entirely and let the function go back to plain &lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt;. For now, the local variable does the job.&lt;/p&gt;
&lt;h2 id=&quot;why-my-tests-didnt-catch-it&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-my-tests-didnt-catch-it&quot; aria-label=&quot;why my tests didnt catch it permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why my tests didn’t catch it&lt;/h2&gt;
&lt;p&gt;Now to the testing side. I had a careful suite of integration tests for the stored procedure. Two instances racing for the lock. The same instance reconnects after a crash. Takeover after a custom timeout. They all passed, and they could not have caught this bug. Here’s the shape of a typical one:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  lock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tryAcquire&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; execute&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s the shape: set up some state, call &lt;code class=&quot;language-text&quot;&gt;tryAcquire&lt;/code&gt; once, check the result. A single call to the stored procedure works fine with &lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt; in the WHERE clause. There is only one timestamp involved per call, and the predicate evaluates correctly against it. The bug only shows up when several calls share one transaction, which happens when the consumer’s &lt;code class=&quot;language-text&quot;&gt;withTransaction&lt;/code&gt; wraps a retry policy. The stored-procedure tests never put those two together.&lt;/p&gt;
&lt;p&gt;The end-to-end consumer tests do go through the consumer’s &lt;code class=&quot;language-text&quot;&gt;withTransaction&lt;/code&gt; wrapper, but they covered the happy paths: clean start, clean stop, two consumers competing, and an instance reclaiming its own stale lock. None of them combined the three conditions that together expose the bug:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;a previous owner whose row still says &lt;code class=&quot;language-text&quot;&gt;status = &apos;running&apos;&lt;/code&gt; (a crash, not a graceful stop),&lt;/li&gt;
&lt;li&gt;a new instance arriving with a different instance ID,&lt;/li&gt;
&lt;li&gt;a retry acquisition policy with a timeout short enough for the retries to outlast it inside the test’s deadline.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Any one of those missing and the takeover predicate either succeeded on the first attempt (so the retry never fired), or the test finished before the retry’s failure to make progress was visible.&lt;/p&gt;
&lt;p&gt;So why did this slip through both layers? The stored-procedure tests never combined a retry policy with the stale-row state, so they never produced multiple calls inside one transaction. The end-to-end tests did exercise the retry path, but none of them happened to combine all three conditions above at once. Both layers had blind spots, and the bug lived exactly where they overlapped.&lt;/p&gt;
&lt;p&gt;The Pull Request that fixes the bug also adds a new family of tests that mount &lt;code class=&quot;language-text&quot;&gt;tryAcquire&lt;/code&gt; under the same transactional wrapper the real consumer uses, with the crash + new-instance + retry combination wired up on purpose. That’s the kind of test I should have had from the start.&lt;/p&gt;
&lt;h2 id=&quot;what-im-taking-away&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-im-taking-away&quot; aria-label=&quot;what im taking away permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What I’m taking away&lt;/h2&gt;
&lt;p&gt;Two things about &lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt; is the right tool when you want every row touched in the same transaction to share a timestamp.&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;created_at&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;last_updated&lt;/code&gt;, audit columns. That stability is a feature.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt; is the wrong tool when you want to ask “has time moved on?” from inside a transaction.&lt;/strong&gt; Use &lt;code class=&quot;language-text&quot;&gt;clock_timestamp()&lt;/code&gt; when you genuinely mean the wall clock.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And one harder thing about tests. Inner tests for stored procedures give me a tight feedback loop and pinpoint failures, but only inside the scaffolding I build for them. End-to-end tests run the real wiring, but I can’t enumerate every combination of timeouts, instance IDs and crash states without the suite collapsing under its own weight. The combination where this bug lived wasn’t reachable from either side by accident; it needed a deliberate setup.&lt;/p&gt;
&lt;p&gt;I don’t have a clean rule for where to draw that line, and honestly, I don’t think there is one. My takeaway is to look at the seam: the spot where the inner test invokes the code differently from how the production caller invokes it. Here, it was a single-call test against a retry loop sharing one transaction. Wherever that gap sits, write a test there. Not at the unit level, not at the full end-to-end level, but in a setup that mirrors how the real caller actually drives the code and exercises the path you care about.&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt; returns the start of the current transaction, not the current moment. Inside a transaction it doesn’t change between statements. If you wrap a retry loop around a function that uses &lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt; in a &lt;code class=&quot;language-text&quot;&gt;WHERE&lt;/code&gt; clause, and the retry loop runs inside one transaction, the predicate is frozen and the retries do nothing. Use &lt;code class=&quot;language-text&quot;&gt;clock_timestamp()&lt;/code&gt; when you mean “right now”. And pay extra attention to the seam between inner and end-to-end tests, because that’s where mismatches between how tests drive the code and how production drives it tend to hide.&lt;/p&gt;
&lt;p&gt;The fix and the new tests live in &lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/339&quot;&gt;Emmett Pull Request #339&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Uff. That bug was nasty.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/800452215e74cd33b63bafd5c96e24d1/2026-05-25-nasty.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Read also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/rebuilding_event_driven_read_models/&quot;&gt;Rebuilding Event-Driven Read Models in a safe and resilient way&lt;/a&gt;, with the locking design this bug lives inside,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/distributed-locking-a-practical-guide/&quot;&gt;Distributed Locking: A Practical Guide&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/consumers_processors_in_emmett/&quot;&gt;Consumers, projectors, reactors and all that messaging jazz in Emmett&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/checkpointing_message_processing/&quot;&gt;Checkpointing the message processing&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cybertec-postgresql.com/en/postgresql-now-vs-nowtimestamp-vs-clock_timestamp/&quot;&gt;Cybertec: PostgreSQL &lt;code class=&quot;language-text&quot;&gt;now()&lt;/code&gt; vs &lt;code class=&quot;language-text&quot;&gt;now()::timestamp&lt;/code&gt; vs &lt;code class=&quot;language-text&quot;&gt;clock_timestamp()&lt;/code&gt;&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/is_keeping_utc_dates_best_solution/&quot;&gt;Is keeping dates in UTC really the best solution?&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Or my other articles about PostgreSQL:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/postgres_superpowers/&quot;&gt;Postgres Superpowers in Practice&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/postgresql-partitioning-logical-replication/&quot;&gt;PostgreSQL partitioning, logical replication and other Q&amp;#x26;A&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/postgresql-jsonb-powerful-storage/&quot;&gt;PostgreSQL JSONB - Powerful Storage for Semi-Structured Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/push_based_outbox_pattern_with_postgres_logical_replication/&quot;&gt;Push-based Outbox Pattern with Postgres Logical Replication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_get_all_messages_through_postgres_logical_replication/&quot;&gt;How to get all messages through Postgres logical replication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/the-write-ahead-log-a-foundation/&quot;&gt;The Write-Ahead Log: The underrated Reliability Foundation for Databases and Distributed systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/ordering_in_postgres_outbox/&quot;&gt;How Postgres sequences issues can impact your messaging guarantees&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[On mashing up modelling techniques for fun and profit]]></title><description><![CDATA[Many people believe there should be one, and only one, way to model software. I think differently, I like to mix different techniques. For…]]></description><link>https://event-driven.io/en/on-mashing-up-modelling-techniques/</link><guid isPermaLink="false">https://event-driven.io/en/on-mashing-up-modelling-techniques/</guid><content:encoded>&lt;p&gt;Many people believe there should be one, and only one, way to model software. I think differently, I like to mix different techniques.&lt;/p&gt;
&lt;p&gt;For instance, I believe there’s a strong synergy between &lt;a href=&quot;https://c4model.com/&quot;&gt;C4 Model&lt;/a&gt;, &lt;a href=&quot;https://github.com/ddd-crew/context-mapping&quot;&gt;Context Maps&lt;/a&gt;, and &lt;a href=&quot;https://www.eventstorming.com/&quot;&gt;EventStorming&lt;/a&gt;. They all allow us to look at the system from a different angle and act as simulations, providing different feedback on whether our model will fly.&lt;/p&gt;
&lt;p&gt;Look below for the diagram I prepared for my upcoming &lt;a href=&quot;/en/training/&quot;&gt;workshop&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5ed088d85a6e4f737c6da5b2c7eaabca/ba096/2026-05-18-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 63.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAECAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAv/aAAwDAQACEAMQAAAB20hLCZL/AP/EABgQAAMBAQAAAAAAAAAAAAAAAAABERAh/9oACAEBAAEFAm8VITp//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFxAAAwEAAAAAAAAAAAAAAAAAAAEQEf/aAAgBAQAGPwIWRCn/xAAcEAACAgIDAAAAAAAAAAAAAAABEQBBEGEhUXH/2gAIAQEAAT8hART3GaNwOT7iAgn7ECgMf//aAAwDAQACAAMAAAAQ2D//xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQMBAT8Qyq//xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQIBAT8QmI//xAAcEAADAAIDAQAAAAAAAAAAAAABESEAMUFRYRD/2gAIAQEAAT8QMEUQIw8LgLeNTzAB6FXMjLk6L7feAP5Smvn/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5ed088d85a6e4f737c6da5b2c7eaabca/507e6/2026-05-18-cover.webp 200w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/28a80/2026-05-18-cover.webp 400w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/8d2ea/2026-05-18-cover.webp 800w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/68fc1/2026-05-18-cover.webp 1200w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/43d96/2026-05-18-cover.webp 1600w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/e1665/2026-05-18-cover.webp 8788w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5ed088d85a6e4f737c6da5b2c7eaabca/37402/2026-05-18-cover.jpg 200w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/4cda9/2026-05-18-cover.jpg 400w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/c60e9/2026-05-18-cover.jpg 800w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/6c738/2026-05-18-cover.jpg 1200w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/56dca/2026-05-18-cover.jpg 1600w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/ba096/2026-05-18-cover.jpg 8788w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5ed088d85a6e4f737c6da5b2c7eaabca/c60e9/2026-05-18-cover.jpg&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;It’s a C4 diagram showing containers in a hospitality system (so-called Property Management, where property means hotel).&lt;/p&gt;
&lt;p&gt;A container gives quick feedback on how many pieces I’ll potentially need to manage and deploy, and it also shows me boundaries.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ddd-crew/context-mapping&quot;&gt;Context Maps&lt;/a&gt; help in understanding how they relate to each other, which module or team has bigger leverage, and can force more on others. It can also show me the information flow and simulate which module will expose the API.&lt;/p&gt;
&lt;p&gt;I can then take some business process and have a look at how the message flow will look. This can happen as an early simulation or after &lt;a href=&quot;https://www.eventstorming.com/&quot;&gt;EventStorming&lt;/a&gt; sessions.&lt;/p&gt;
&lt;p&gt;Then C4 allow me to also look inside the container, show components if I need to understand more details. I can group containers if my bounded context has more of them.&lt;/p&gt;
&lt;p&gt;How to start with it? Let me show you an example.&lt;/p&gt;
&lt;p&gt;The starting point could be understanding the system’s overall view; integrations with external systems usually introduce the most complexity. C4 System Diagram works well for that.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a97c45c352616574aaaf4c06f1901aa6/20aff/2026-05-18-system-view.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 99%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQADAAAAAAAAAAAAAAAAAAMBAgX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB706zNk1WwQB//8QAGRABAAMBAQAAAAAAAAAAAAAAAQACERIi/9oACAEBAAEFAllecq6R81pY5iaYT//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABYRAQEBAAAAAAAAAAAAAAAAAAEAEP/aAAgBAgEBPwGTf//EABkQAAMAAwAAAAAAAAAAAAAAAAAQEQIhUf/aAAgBAQAGPwIuOyrji//EABwQAQACAgMBAAAAAAAAAAAAAAEAERBRITFhQf/aAAgBAQABPyGhCnn7qJWj0Mo+l4onFDUITXmAZdMCKCf/2gAMAwEAAgADAAAAEHsvgv/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8QH//EABgRAAMBAQAAAAAAAAAAAAAAAAABESEx/9oACAECAQE/EMeDK4LpT//EABwQAQACAgMBAAAAAAAAAAAAAAEAESExEGFxwf/aAAgBAQABPxBL6LWNepU4I5CS1DojprcDaBOnwli1cFF7xcTRTUNBA6n/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/a97c45c352616574aaaf4c06f1901aa6/507e6/2026-05-18-system-view.webp 200w,
/static/a97c45c352616574aaaf4c06f1901aa6/28a80/2026-05-18-system-view.webp 400w,
/static/a97c45c352616574aaaf4c06f1901aa6/8d2ea/2026-05-18-system-view.webp 800w,
/static/a97c45c352616574aaaf4c06f1901aa6/68fc1/2026-05-18-system-view.webp 1200w,
/static/a97c45c352616574aaaf4c06f1901aa6/43d96/2026-05-18-system-view.webp 1600w,
/static/a97c45c352616574aaaf4c06f1901aa6/14820/2026-05-18-system-view.webp 4021w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/a97c45c352616574aaaf4c06f1901aa6/37402/2026-05-18-system-view.jpg 200w,
/static/a97c45c352616574aaaf4c06f1901aa6/4cda9/2026-05-18-system-view.jpg 400w,
/static/a97c45c352616574aaaf4c06f1901aa6/c60e9/2026-05-18-system-view.jpg 800w,
/static/a97c45c352616574aaaf4c06f1901aa6/6c738/2026-05-18-system-view.jpg 1200w,
/static/a97c45c352616574aaaf4c06f1901aa6/56dca/2026-05-18-system-view.jpg 1600w,
/static/a97c45c352616574aaaf4c06f1901aa6/20aff/2026-05-18-system-view.jpg 4021w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/a97c45c352616574aaaf4c06f1901aa6/c60e9/2026-05-18-system-view.jpg&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We see actors representing our system users and interactions with external systems. We can decide what integration is out of scope for now, and may come as the second step, but not for now. We can also show the current state as is, then in the second diagram show our vision of what we want it to be. Be creative, this is not a relational database, we don’t need to normalise it.&lt;/p&gt;
&lt;p&gt;Then we may decide to dive into what we need to build to facilitate that, make a bet based on our current knowledge, zoom in and check the C4 Container view.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/abf135832c0f3e18d6895ebf55323fce/cdcc8/2026-05-18-container-view.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsTAAALEwEAmpwYAAACNklEQVR42nVTW2sTURDOf/OX+OCLf8AHwf8gKCiC+KD4oAhV8EIReqFqYkK1VnsxbROSNG1uu5uzu+fM7RznbGpLqQ6z5zrfzDczZ2uhEhEv7LLRF6Fct957HY+OjlqtVr1ebzab1tpwRWr6cSXqAaBkwvM7PSQirMRX/sSfXelGNYIRrV4hkskLC6QWkYiOEeGrsVpXjM7xMbIt/XEPADx7Wd6EW0/sYEqWgnF0Ovo1S3uj8V4Ooier2/Rxiy2KMhifUL9DtU6bXjyeT8dA3u8MZKmBUyPGSQ48TU+T+WyWjeeWCqBHH9z9t2CRxdPeD9vasLVISYKGTnN0JJpjgSHJMS0ws34yxxx8kkNWsHEhh2DJXyrYotq54+OZdEYaJKSFAOLLDXfnmVNHWckleCQoQApgcLz6vuge4BmYJUjgB++idYmSFJQ7bLbxTcNlpZToexN5vgYHJwys7ORb3Q77dA72anFwyjt9VtpKLwZxCITGsiXZ7dPtp7B5iKjNRK6vlL3Dv5G1Cey9Y29KyEoyEAZTnBUyMazFN9FXUKeqGuMi5+othb1usrTWnRqtitesehO+ftc2fkujLTfu2c5Y/UY6qgvw2SPhqusPX/28dvN1+ziPRs7PDK9vl8PEDxP5tAtJGRa5GLgceTGlBjrDOZIsMiAWEdRXFee4iO3gSkWugBdM4s+wD98bJUL4vFKkM56MeH0511cY/iUXYIlRQ79L+9taY7/VdCaTZMJf10v3H/AfFCVWxP6j2aIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/abf135832c0f3e18d6895ebf55323fce/507e6/2026-05-18-container-view.webp 200w,
/static/abf135832c0f3e18d6895ebf55323fce/28a80/2026-05-18-container-view.webp 400w,
/static/abf135832c0f3e18d6895ebf55323fce/8d2ea/2026-05-18-container-view.webp 800w,
/static/abf135832c0f3e18d6895ebf55323fce/68fc1/2026-05-18-container-view.webp 1200w,
/static/abf135832c0f3e18d6895ebf55323fce/43d96/2026-05-18-container-view.webp 1600w,
/static/abf135832c0f3e18d6895ebf55323fce/a6ce8/2026-05-18-container-view.webp 4669w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/abf135832c0f3e18d6895ebf55323fce/36ca5/2026-05-18-container-view.png 200w,
/static/abf135832c0f3e18d6895ebf55323fce/a3397/2026-05-18-container-view.png 400w,
/static/abf135832c0f3e18d6895ebf55323fce/a331c/2026-05-18-container-view.png 800w,
/static/abf135832c0f3e18d6895ebf55323fce/8537d/2026-05-18-container-view.png 1200w,
/static/abf135832c0f3e18d6895ebf55323fce/1a152/2026-05-18-container-view.png 1600w,
/static/abf135832c0f3e18d6895ebf55323fce/cdcc8/2026-05-18-container-view.png 4669w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/abf135832c0f3e18d6895ebf55323fce/a331c/2026-05-18-container-view.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As mentioned, this can already be used both for static documentation and for discussions during a workshop, simulating the potential complexity and boundaries. Important to note: until it’s settled, it’s more than fine to show different versions, discuss them, and add notes and questions.&lt;/p&gt;
&lt;p&gt;We may have concluded that C4 is fine, but it doesn’t capture all the important reasons we modelled it this way. Or during the workshop, it may not be clear how to discuss precisely how to break it down. &lt;a href=&quot;https://github.com/ddd-crew/context-mapping&quot;&gt;Context Maps&lt;/a&gt; can help in that.&lt;/p&gt;
&lt;p&gt;They can give us information on which module is generic (Open Host), which module (or team) is more important (upstream), and which module or team has less leverage (Downstream). Which provides api or data (Supplier) and which one is using it (Consumer).&lt;/p&gt;
&lt;p&gt;We could have a dedicated model only focusing, on that, but we could also capture it in the same one. It could look as follows:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8868dc9788df58b92f6e7e2ba0720f9b/36626/2026-05-18-context-map.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAAsTAAALEwEAmpwYAAACR0lEQVR42m2SW2sTQRTH802lX8GnYp989MU3QVCoUATxAg0i3itptdJSTa1tk/SepOtms9nd2d3ZuZ2ZOZ5sSaW2h9mBYc/v/M+tgbU5560zRbxpIaen957uKIra7fbW1tbu7q5zDq9Zgz5bGyFKCQB9+Y8AMGZ6wEyfHuuYV2FtpPMOrDVgtQb05Oddfc8MLyiPV/iGVn4UaKEcF2Jtz8wv6Z0zJoznykVxJ8uDcdyrjB0nqrlh9k7VVMCYKDR56hrhEJ49yvcPYuVtt6+fvo8HiSqlLZWbpKM0TxIW5dIcHowWnlRvV8dKigljrU+ss60blIYFyhySUvNKCZYqh2mpCwm5cjHTuXRZaSqLuUJOnuCv1Fz3zAnt+rHc6ecKkEmfxJMXr4d3lmT3aFAqL4D6Z0sJ5DaapK0PxfAEZrBHkSUPm+ntRT6Ms4wbxvi7z73F5ZPzP5NS2bOger6qO6cVFc1K/uO7CAczmBqrwPcCsd7NuKZukZRPi7LiBCLX0P41WlgUqxuRVCrKsm9feP/4EnZ+mpvxFJlg4bC7f57mesKhG7BSofJIUaRDdb1mGiX4qWDGIRfmqBfO3ctae6596m7drw5Dr6znGklAmtnkPTZIk+BXK8fzDzbiwlYGC+pWYT62gkEMYQorX8OEIzdYaiw0EvxP2dbw4+X9ubsrNJK6BGscsipVAMrYQjCw0wldHLDX0sbZ3p0d6s01TpN/0xzTGiUxNF9GvHSI/y/2DfAogN5vDcZ3tk2RuTxzP9e1qPyN8F9U0k/4GBD34AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/8868dc9788df58b92f6e7e2ba0720f9b/507e6/2026-05-18-context-map.webp 200w,
/static/8868dc9788df58b92f6e7e2ba0720f9b/28a80/2026-05-18-context-map.webp 400w,
/static/8868dc9788df58b92f6e7e2ba0720f9b/8d2ea/2026-05-18-context-map.webp 800w,
/static/8868dc9788df58b92f6e7e2ba0720f9b/68fc1/2026-05-18-context-map.webp 1200w,
/static/8868dc9788df58b92f6e7e2ba0720f9b/43d96/2026-05-18-context-map.webp 1600w,
/static/8868dc9788df58b92f6e7e2ba0720f9b/c1758/2026-05-18-context-map.webp 4656w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/8868dc9788df58b92f6e7e2ba0720f9b/36ca5/2026-05-18-context-map.png 200w,
/static/8868dc9788df58b92f6e7e2ba0720f9b/a3397/2026-05-18-context-map.png 400w,
/static/8868dc9788df58b92f6e7e2ba0720f9b/a331c/2026-05-18-context-map.png 800w,
/static/8868dc9788df58b92f6e7e2ba0720f9b/8537d/2026-05-18-context-map.png 1200w,
/static/8868dc9788df58b92f6e7e2ba0720f9b/1a152/2026-05-18-context-map.png 1600w,
/static/8868dc9788df58b92f6e7e2ba0720f9b/36626/2026-05-18-context-map.png 4656w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/8868dc9788df58b92f6e7e2ba0720f9b/a331c/2026-05-18-context-map.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We can even use colours, e.g., upstream as green and downstream as red. This can already give us feedback, e.g., that Cashiering is an important module for financial processing and that it is downstream of multiple modules. Maybe we can change it and redefine boundaries, or maybe live with it, but take some corrective actions.&lt;/p&gt;
&lt;p&gt;We can also put more data and use a specific example of a process and have a look at the specific process, as shown in the original diagram:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5ed088d85a6e4f737c6da5b2c7eaabca/ba096/2026-05-18-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 63.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAECAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAv/aAAwDAQACEAMQAAAB20hLCZL/AP/EABgQAAMBAQAAAAAAAAAAAAAAAAABERAh/9oACAEBAAEFAm8VITp//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFxAAAwEAAAAAAAAAAAAAAAAAAAEQEf/aAAgBAQAGPwIWRCn/xAAcEAACAgIDAAAAAAAAAAAAAAABEQBBEGEhUXH/2gAIAQEAAT8hART3GaNwOT7iAgn7ECgMf//aAAwDAQACAAMAAAAQ2D//xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQMBAT8Qyq//xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQIBAT8QmI//xAAcEAADAAIDAQAAAAAAAAAAAAABESEAMUFRYRD/2gAIAQEAAT8QMEUQIw8LgLeNTzAB6FXMjLk6L7feAP5Smvn/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5ed088d85a6e4f737c6da5b2c7eaabca/507e6/2026-05-18-cover.webp 200w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/28a80/2026-05-18-cover.webp 400w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/8d2ea/2026-05-18-cover.webp 800w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/68fc1/2026-05-18-cover.webp 1200w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/43d96/2026-05-18-cover.webp 1600w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/e1665/2026-05-18-cover.webp 8788w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5ed088d85a6e4f737c6da5b2c7eaabca/37402/2026-05-18-cover.jpg 200w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/4cda9/2026-05-18-cover.jpg 400w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/c60e9/2026-05-18-cover.jpg 800w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/6c738/2026-05-18-cover.jpg 1200w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/56dca/2026-05-18-cover.jpg 1600w,
/static/5ed088d85a6e4f737c6da5b2c7eaabca/ba096/2026-05-18-cover.jpg 8788w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5ed088d85a6e4f737c6da5b2c7eaabca/c60e9/2026-05-18-cover.jpg&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This can also pinpoint some issues, e.g., a generic module, which is more likely to expose commands (see more on why in my article on Passive-Aggressive Events). We can see if we have all the needed ACLs defined, or which module acts as a coordinator.&lt;/p&gt;
&lt;p&gt;Of course, keeping it all on the same diagram is not perfect; it’d be great if we had a tool that would enable zoom-in/zoom-out, link and enable/disable some of the details and notation. Then we can even better play with what we have. Maybe I should vibe such?&lt;/p&gt;
&lt;p&gt;Nevertheless, I encourage you to try different techniques, experiment, and combine them. For instance, Example Mapping would, imho, play great here as an extension. I know that some use Wardley Maps or Domain Storytelling.&lt;/p&gt;
&lt;p&gt;Start small, use existing techniques and tools, and try to see how you could benefit from mixing them. Show it to your friends and try to collaborate and have fun together.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7461032293072195585?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7461032293072195585%2C7461050717479346176%29&amp;#x26;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287461050717479346176%2Curn%3Ali%3Aactivity%3A7461032293072195585%29&quot;&gt;I even learned from Richard Gross that this has an even name: Model Storming&lt;/a&gt;. So, crunching your design with different modelling techniques and coming up with some useful variations.&lt;/p&gt;
&lt;p&gt;I have even more mashups like that. Tell me if you’d like me to expand on it or this part in the follow-up articles!&lt;/p&gt;
&lt;p&gt;Also, if you did such experiments, please share them with me and others. Happy to see what you came up with.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Don't overestimate domain expertise]]></title><description><![CDATA[I was toying with LLM-based domain research. It reminded me of the common mistake we make when we try to practice DDD: overreliance on what…]]></description><link>https://event-driven.io/en/should_we_trust_domain_experts/</link><guid isPermaLink="false">https://event-driven.io/en/should_we_trust_domain_experts/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/91c2ca425f1d8b7da7de43878c7a64a5/0a70b/2026-05-11-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC50lEQVR42gHcAiP9ADRXPi1ROCpONS5NOCVHMGyAbtTQx9PTy8XDu9TCqtnJtefn3+Ti1+Dc0eHbz9bSxtnZ09fY0ZullGN4WAB2a0qEhGhsg3dFSUI0Q0c4TlRLZXBfamxxeXeEfW6Of2bZzbrm5dy9taZ8dWe9u7GqoojDw7Vub0h0VzoAb5CRdJOLmJ2PV1RKFSw5K0NPPl1wQVVgTV9oXnF5WlVLtKmY+fLisJ2NgFpEloNwnZV6uLamvKN6uI14ADRmWTdjU1poT0VORB0yPRMmMjhOWUpjb0JYZUNKSodjTXNiTcS1mM22oYNaQKZyROq4hKyRe4xuR6KbhAAqTDE3UjlLQCxOPipLR0EMMksuRldWX2NGXGhHRDpAMB9OQTFCRD2hjXGPYzBqTCtqUjOdel6geUusoYMAJ0syK000SkMzgFI3emFSDDFGETRPLjpAVlFEaFhFW1A+aVhDZGJVmpSGj2xMf15GJh0WTz4mhGZOinlkAB9EKmBxXb6jjYBfSyk3QAYjNgkkOgooQB0zPl9YTG9kUl9dUlpYToRuXJZ2Y5tuUpVnTqSEZYtYPZN9ZgA0TTeUiXmcgW1FRTwDHig0RksqNzUpNTIuPTwRHhxreXnI3uF1enNYSj1ZTkJ0Vz+Nf2uZkoKnl4V9dGUAMzktZVhIjXNfkol4u8C21s3ClZaIg4NwX2RfU1BFUFROrsTJnpiLdmVUlndjkXtoXl9RLS0gV1RGRkY7AD02KkE2J3NfTI98aqiUgsWfhLGfib2tl7SnlcGhfpR+Ym9vaK2NdbOKcMCQco92YCUoHRseFQsRCjU2LwBDPTExLiQqKiIrLys2LiBzXkKHbEy2kWTawJ/r5+Dv6t/j4Ne2nHt9Wjx6WUVWRjUZHxUWGxMQEwwhIR0AChYYBQ8PBhkfAB0rLDc1Gh4aMzcyV1FFVEMveGBEqIdgjXFPNCsdAQIBChEPKzMuIiYcFhoTBgoGSTkl1jAfuwJYEuQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/91c2ca425f1d8b7da7de43878c7a64a5/507e6/2026-05-11-cover.webp 200w,
/static/91c2ca425f1d8b7da7de43878c7a64a5/28a80/2026-05-11-cover.webp 400w,
/static/91c2ca425f1d8b7da7de43878c7a64a5/8d2ea/2026-05-11-cover.webp 800w,
/static/91c2ca425f1d8b7da7de43878c7a64a5/68fc1/2026-05-11-cover.webp 1200w,
/static/91c2ca425f1d8b7da7de43878c7a64a5/ab917/2026-05-11-cover.webp 1317w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/91c2ca425f1d8b7da7de43878c7a64a5/36ca5/2026-05-11-cover.png 200w,
/static/91c2ca425f1d8b7da7de43878c7a64a5/a3397/2026-05-11-cover.png 400w,
/static/91c2ca425f1d8b7da7de43878c7a64a5/a331c/2026-05-11-cover.png 800w,
/static/91c2ca425f1d8b7da7de43878c7a64a5/8537d/2026-05-11-cover.png 1200w,
/static/91c2ca425f1d8b7da7de43878c7a64a5/0a70b/2026-05-11-cover.png 1317w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/91c2ca425f1d8b7da7de43878c7a64a5/a331c/2026-05-11-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I was toying with LLM-based domain research. It reminded me of the common mistake we make when we try to practice DDD: overreliance on what domain experts are telling us.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I wanted to remind myself of the domain I used to know: hospitality management, to adjust my upcoming &lt;a href=&quot;/en/training/#event-driven-architecture-the-light-and-the-dark-side&quot;&gt;workshop&lt;/a&gt;. I selected LLM (Claude Opus) as a sparing partner. And boy, I got a loooot of details. I was swamped by them. The LLM modelled everything: marketing consent, loyalty timing, inventory management, revenue posting, regulatory submissions, data retention policies, all at the same level of detail, which buried the core checkout flow under noise, the thing I asked about.&lt;/p&gt;
&lt;p&gt;That’s not much different from initial work with domain experts. When we’re starting discovery, people tend to start by explaining everything they do and feel is important. Quite often, that means you’ll get more domain-expert pet peeves in the surroundings than in the process descriptions.&lt;/p&gt;
&lt;p&gt;You also get a lot of jargon, words that sound familiar but mean something different.&lt;/p&gt;
&lt;p&gt;That’s also what I got from LLM, I asked it to review reference sources like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;popular tools documentation,&lt;/li&gt;
&lt;li&gt;open specifications from the hospitality organisations,&lt;/li&gt;
&lt;li&gt;some laws like GDPR related stuff,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I researched how different tools handle the guest checkout process. I knew it, and implemented it in past; it’s a surprisingly complex process, as you need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;verify if the stay is fully paid and close the financial account,&lt;/li&gt;
&lt;li&gt;generate invoice&lt;/li&gt;
&lt;li&gt;mark guest’s stay as completed,&lt;/li&gt;
&lt;li&gt;schedule full room cleanup,&lt;/li&gt;
&lt;li&gt;mark in the inventory that the room will be available soon (unless the maid finds that it’s broken),&lt;/li&gt;
&lt;li&gt;cleanup GDPR-related data that’s not needed to keep,&lt;/li&gt;
&lt;li&gt;adjust loyalty points and update CRM,&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I remembered how it works, but not all the details, and I would like to double-check whether anything in the industry has changed.&lt;/p&gt;
&lt;p&gt;I wanted to get the overall vision first, then gradually dive deeper. As mentioned, I was overwhelmed with naming like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Folio,&lt;/li&gt;
&lt;li&gt;Drain Pending postings,&lt;/li&gt;
&lt;li&gt;Property,&lt;/li&gt;
&lt;li&gt;Account Receivables,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;p&gt;Don’t get me wrong, those were valid names in these domains. Sounds plausible if you’ve never worked in hospitality. Sounds plausible if you have, too. So what’s wrong with it?&lt;/p&gt;
&lt;p&gt;They’re valid terms, as people actually use them, but weird, as they don’t tell “how the process works” but “how people do stuff,” and that’s not the same thing.&lt;/p&gt;
&lt;p&gt;For instance, Property is a hotel, kind of makes sense. But Folio?&lt;/p&gt;
&lt;p&gt;This name comes from the Oracle Opera. Yes, Oracle has a system in this domain, a dominating one. At some point, authors decided to name things this way, and now 30 years later, that vocabulary is baked into how thousands of hoteliers talk about their work. “Drain pending postings” is a phrase real cashiers say.&lt;/p&gt;
&lt;p&gt;“Settle the folio” is a thing real cashiers do.&lt;/p&gt;
&lt;p&gt;The problem: none of it explains how our system should work. It only tells what current systems do.&lt;/p&gt;
&lt;p&gt;Also, when I asked about the business rules and policies, I got stuff like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The system immediately posts room and tax charges for day-use reservations upon opening the Billing screen.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Why are transactions such as night stay charges or taxes added when we open the billing screen? That sounds counterintuitive, but maybe it was a fair tradeoff 30 or 20 years ago for an on-premises system installed in the specific hotel (ekhm “property|). Yes, Opera had (and maybe still had) consultants, similar to SAP. They’d go to the hotel, go to the back office, log in to the server and hack stored procedures in the Oracle database to fine-tune Opera behaviour.&lt;/p&gt;
&lt;p&gt;Also, is there really a “Draining Pending Postings” option before doing checkout nowadays? Maybe it is, but in modern systems, we shouldn’t manually check all bills, etc., and explicitly pull them from integrated payment solutions. Nowadays, all the accommodation charges should already be recorded on the bill. The financial module continuously collects charges from payment gateways throughout the stay. There’s no separate moment of “draining”. The LLM invented a coordination command to match a phrase (“drain the interfaces”) that real cashiers use as shorthand for “let me check that nothing’s outstanding.”&lt;/p&gt;
&lt;p&gt;As I asked, LLMs researched systems in this space: OPERA, Mews, Apaleo, and Cloudbeds. Each has its own vocabulary and mechanics, and the training data heavily favours OPERA because its documentation is everywhere. When I pushed back on terminology, the model would just swap in different OPERA jargon instead of actually thinking about what’s modern. The blending happened invisibly, mixing vocabulary from one system with mechanics from another, all delivered with equal confidence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To be fair, the same happens quite often when talking to domain experts. They explain how it works now and what people do.&lt;/strong&gt; Quite often, they bring us &lt;a href=&quot;/en/bring_me_problems_not_solutions/&quot;&gt;solutions instead of problems&lt;/a&gt;. Usually, solutions are based on their experience and how they see the updated version. This can be fine as a brain dump, but it’s not enough to translate it directly into the software design.&lt;/p&gt;
&lt;p&gt;In Domain-Driven Design, finding and understanding the ubiquitous language is considered the most important aspect. &lt;strong&gt;Yet, Ubiquitous Language is not the source of truth.&lt;/strong&gt; It’s the way to keep our heads from exploding due to the constant split-brain situation. It’s a tool to reduce cognitive load and the need for additional translation. That’s why we separate domain contexts and bind them to specific departments, people, and the language they use.&lt;/p&gt;
&lt;p&gt;It’s fine to start with the current state of the art. Understanding how people do their job. We need to understand, though, that what we get is a mixture of habits (both good and bad), tribal knowledge, jargon, etc. If you ask different tribes, each will tell you something different.&lt;/p&gt;
&lt;p&gt;Domain language is a cognitive tool, not gospel. LLMs compound this problem by reiterating competitor vocabulary without understanding the reasoning behind those systems, and they tend to align with whatever the prompter already believes.&lt;/p&gt;
&lt;p&gt;Domain experts also struggle to define what they want and how it should work. That’s also why they hire us. It’s our job to help them and to transfer those sometimes contradicting visions into working software. That’s what we’re learning and what we do when modelling and step-by-step shaping the working software. That’s our work as engineers. We should work together to have a proper outcome. Collaborate.&lt;/p&gt;
&lt;p&gt;And I’m not making the bold statement here that we’re smarter or we know better. We’re not; we have different expertise and different roles in the software development process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Contrary to what many people believe in the DDD community, in my opinion, we’re not here to become domain experts; we’re here to build software.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The model we built doesn’t have to reflect the whole universe; it’s a way to take part of the business, understand, and automate it in the form of a software system. A software system is a tool for the business make more money. So software needs to be useful in a certain, defined way. Not all possible ways.&lt;/p&gt;
&lt;p&gt;We need to learn &lt;a href=&quot;/en/a_few_words_on_communication/&quot;&gt;how to communicate&lt;/a&gt; with business people, for instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don’t use jargon or acronyms or assume someone should know something.&lt;/li&gt;
&lt;li&gt;Understand that what someone wants to tell us might not be what they hear.&lt;/li&gt;
&lt;li&gt;Do not take others’ behaviour personally.&lt;/li&gt;
&lt;li&gt;Be assertive, critical and sceptical. Also, to our own judgments.&lt;/li&gt;
&lt;li&gt;Be curious about the business domain. Don’t assume too much.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/business-wont-let-me-and-other-lies/&quot;&gt;Don’t use “business won’t let me” as an easy excuse&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For some people, that’s too much. That’s probably why they try to ask LLM instead of Domain Experts, hoping that we won’t need to learn that, and we’ll get a solution for free.&lt;/p&gt;
&lt;p&gt;We’re sometimes annoyed by being pushed hard by business people, I get that, but if we want to build something useful, that’s also where LLM will fail, as they will just agree eventually to what we believe. And that’ll probably be even worse than the skewed reality shown by the domain expert, since this will be Artificial Reality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I keep hearing blank statements that “LLMs are great for research”. Blank because they usually are not followed up with what the author means by “great”.&lt;/strong&gt; LLMs have many inherent limitations here. We’re anthropomorphising them too much. They don’t reason; they’re statistical machines. We should constantly remind ourselves of that.&lt;/p&gt;
&lt;p&gt;I think people who claim that “LLMs are great at research” are just conflating their own skills and projecting them onto LLMs.&lt;/p&gt;
&lt;p&gt;Hence, in my opinion, that’s why we’re getting those hot takes. Might be that they just have the skills to drill down, organise research, evaluate it, and model system design.&lt;/p&gt;
&lt;p&gt;And I think this narrative is dangerous.&lt;/p&gt;
&lt;p&gt;Because I, too, could sit down and say that I iteratively arrived at a solution thanks to LLMs. Write this article in a much different narrative, praising LLMs. I could say that if someone couldn’t get the same result, then that’s a skill issue.&lt;/p&gt;
&lt;p&gt;But that wouldn’t be true, because someone without my experience in the domain and in modelling probably wouldn’t pick up on it.&lt;/p&gt;
&lt;p&gt;Furthermore, it’s not necessarily true that I did it well; that would just be my perspective.&lt;/p&gt;
&lt;p&gt;In practice, in this context, “LLM does it well” means “LLM does it the way I wanted it to.” And if the outcome is right, it is more likely that the “operator” had the necessary skill and used an LLM as a tool to speed it up.&lt;/p&gt;
&lt;p&gt;The research I did looked at what our competition does and how they name things, but without internal knowledge of why those systems got to where they are. If our goal is to provide additional value to users, then just doing a blind copy won’t take us far. Instead of doing Lift and Shift, building something that has the same features but does better, we’ll get Lift and Shift with silent f.&lt;/p&gt;
&lt;p&gt;Yes, LLMS can gather and compile multiple sources into a summary. That’s actually impressive and can spare us a lot of time. They’re also taught in the public documents, so some of their knowledge is built in. They look impressive on the surface, but not so if you look deeper. In the mentioned research, I got disconnected pieces of information, the techniques were randomly assembled with muddled technical and business language, and the whole thing didn’t tell a coherent story. Because of the randomness, you never know where the knowledge came from, what was omitted and what was skewed. With domain experts, you can at least understand the origin of their biases.&lt;/p&gt;
&lt;p&gt;LLM also has limitations. The biggest is the context size, and being a yes-man. If we ask for the big picture, usually, we’ll end up with a swamp of information instead. If we don’t know how to sort this knowledge, what we’re looking for, and do a proper drill-down, then we won’t be able to untangle it. If we don’t have domain experts and don’t know the domain, how will we challenge issues like the ones I gave above?&lt;/p&gt;
&lt;p&gt;I also keep hearing that LLMs are really useful in modelling. Sure, I tried that. I also asked LLM to try to model, and well, even after numerous iterations and using modelling tools, the results were mediocre. Mostly cliches and bad modelling practices.&lt;/p&gt;
&lt;p&gt;The output muddied what we had before. The context was lost, and the process was oversimplified. When asked to apply specific tools like the C4 model, EventStorming, and Context Maps, it was forcing DDD patterns instead of describing actual processes. Even with precise instructions, it was building models with broken notation where rules floated outside the command-event chains, leaning on clichéd solutions, losing context between iterations. The domain was also presented as a big ball of mud. Concepts from room reservation bleed into the cashiering module. It was a recurring pattern across different versions of the same mistakes.&lt;/p&gt;
&lt;p&gt;If you give too much context, they’ll mix and blend multiple conflicting vocabularies from different operational tribes trying to satisfy you. If you give it too little, it will come with simplistic hallucinations.&lt;/p&gt;
&lt;p&gt;Is it hopeless then? Not at all, we can get help from LLM, but when we use it as a tool, not a replacement. We should not outsource thinking.&lt;/p&gt;
&lt;p&gt;LLMs are useful for grasping the big picture and identifying known unknowns that we may miss as we get into the domain. We can learn about the language specifics, as mentioned in the Folio, Postings, Account Receivables, etc. LLMs can help us drill down into interesting aspects. We can get a brief understanding of domains we don’t know at all. But then we need to dive deeper and collaborate with our domain experts, real stakeholders. We need to understand what we want to build, organise our findings and focus on a certain context.&lt;/p&gt;
&lt;p&gt;If we want to model our system, then LLMs can help us do boring work.  It can organise our findings and create rapid text-based transitions with a defined format (including modelling practices). This can work. Yet it’s still our job to do domain discovery, refine the knowledge, evaluate and test it. We still need to focus on the &lt;a href=&quot;/en/vibing_harness_and_ooda_loops/&quot;&gt;feedback loop with real world&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We won’t hide from gathering modelling skills, we won’t skip the process of translating the domain into technical design, and we’ll still need to drive how and when to drill down. Those are engineering skills that were needed and will still be needed. And that’s fine, as if they wouldn’t, why would someone want to hire us?&lt;/p&gt;
&lt;p&gt;I see that (for unknown reasons) collaboration is not discussed anymore. Working collaboratively with fellow humans has become something people dread rather than embrace. Maybe some people hope that they won’t need to talk to “domain experts” and “fight them”, because they “have everything here in LLM”.&lt;/p&gt;
&lt;p&gt;And yes, they have all.&lt;/p&gt;
&lt;p&gt;All the same issues they would have with domain experts.&lt;/p&gt;
&lt;p&gt;As much as we shouldn’t trust domain experts blindly, but work with them, we shouldn’t blindly trust LLMs. We shouldn’t drop our engineering and design skills and outsource them.&lt;/p&gt;
&lt;p&gt;If we want our software products to be better than the competition, simply blending their experience isn’t enough. We should focus on finding our special sauce, and I don’t see any other way to make it right than to work together and collaborate. We can use LLMs to help us do it faster, but as tools, not as solutions or replacements.&lt;/p&gt;
&lt;p&gt;Check also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/pl/bring_me_problems_not_solutions/&quot;&gt;Bring me problems, not solutions!&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/a_few_words_on_communication/&quot;&gt;A few words on communication&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_design_software_architecture_pragmatically/&quot;&gt;How to design software architecture pragmatically&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/business-wont-let-me-and-other-lies/&quot;&gt;Business Won’t Let Me and other lies we tell to ourselves&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/vibing_harness_and_ooda_loops/&quot;&gt;Vibing, Harness and OODA loop&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/interactive_rubber_ducking_with_gen_ai/&quot;&gt;Interactive Rubber Ducking with GenAI&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/the_end_of_coding_wrong_question/&quot;&gt;The End of Coding? Wrong Question&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/requiem-for-a-10x-engineer-dream/&quot;&gt;Requiem for a 10x Engineer Dream&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/tech-debt-doesnt-exist-but-trade/&quot;&gt;Tech Debt doesn’t exist, but trade-offs do&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Vibing, Harness and OODA loop]]></title><description><![CDATA[Hey, have a look at what I made during the weekend. I had some time, grabbed a beer, turned on the computer and tried to code this feature…]]></description><link>https://event-driven.io/en/vibing_harness_and_ooda_loops/</link><guid isPermaLink="false">https://event-driven.io/en/vibing_harness_and_ooda_loops/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6f23d618ba2fb2ec38a86e77eced2df7/b09c1/2026-04-26-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAACmUlEQVR42nXS609ScRgH8KPYbLP36Qu7qbm0C4lZBFm+MJ3M1KUms9JEMTKZ01aZgJrSxXBe8AKH6+EARw7I4XAOHEEuBzMNs7RaLVutrVf9GR2z2dp0+7543nz2PPv+fkDE691K0Ol2W6yjSqXqmmC0sUbA5w4PKC0avW5sAhxR60bHjROTNhCcMZq2A2zj+VmMRFCbBkQ7RC+V99uKC3vuSCi31wXZEL0Z1oKwRmMHdTvjkAefc7l9qIuQdf6a7g/fE7tAME4vRH2U34nNWqwOoxExGHbGYRwPYZ54NGZ+rFALBcuK9o2F2I9PX9cWl2ifPzCLqQcG9WPjDpMZMRjtBpPdaP6HIwSxGAi6ILi5sYl39PDzOkHL1UqtevrDUjwejkZJv0QoHB8cJByoC7LgMISb9H8wjocZSRAKqTQ1NY3Dyedy+ey8M3xeYdaRzA5J+5fVd8zm0ktFzoH+FdiEPe2LDfX55Z3A1sE0SVCqZzmp+1l7kkuKS+XyPlmPov2uNDMjK5+dt/56FTPoL+efnux+gGi12ifKqV7FlEIGMDJGzSEjw9rqK2cPpgMJCedyTlaUlQvr6quralJS9u1NTvbrtUONQt6J45oXKhdsc0AWJ2xFYesmXqAoVKe7yStQiEVi0a2SAv6NylpRs6RVJDmWnV16KldVU6aXPyIQhxOCmbZmDEztm/lbGE2Sbsi8HqN7ux/mpqUrJV2ytq7yi8VS8e0VksQhKOKjPFa7g5E7PlXES7ylo9LWFgAAuOcLMw4cYgFAc0PDz88b8QgddLsx5mCjeZdPgnneRMNN1+sZzGKxEhMTmaG2quLb2vqrQHAewzc374aZLM8HOGz2Fk5KSmKGogv87+8/LofCIdyL2xD0f/wbcpyqUcQYFvUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/6f23d618ba2fb2ec38a86e77eced2df7/507e6/2026-04-26-cover.webp 200w,
/static/6f23d618ba2fb2ec38a86e77eced2df7/28a80/2026-04-26-cover.webp 400w,
/static/6f23d618ba2fb2ec38a86e77eced2df7/0367d/2026-04-26-cover.webp 626w&quot;
              sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/6f23d618ba2fb2ec38a86e77eced2df7/36ca5/2026-04-26-cover.png 200w,
/static/6f23d618ba2fb2ec38a86e77eced2df7/a3397/2026-04-26-cover.png 400w,
/static/6f23d618ba2fb2ec38a86e77eced2df7/b09c1/2026-04-26-cover.png 626w&quot;
            sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/6f23d618ba2fb2ec38a86e77eced2df7/b09c1/2026-04-26-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hey, have a look at what I made during the weekend. I had some time, grabbed a beer, turned on the computer and tried to code this feature. If I could do so much during the weekend, how much could you and your team do with it in 2 weeks?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s almost a 1:1 quote of what I heard from the startup founder I worked with over 10 years ago. I’m sure that you’ve heard similar phrases from people you worked with. We all know the annoying type of person who doesn’t code anymore but thinks, &lt;em&gt;“I still got it!”&lt;/em&gt;. Then they threw a piece of stuff at you to &lt;em&gt;“just fine-tune it a bit and do final touches”&lt;/em&gt;. Then they’re the first ones to ask “Why so long?“.&lt;/p&gt;
&lt;p&gt;Nowadays, the Internet is full of such people. They shout about what they did with Claude or how much progress LLM tools have made. Some even predict the end of coding. I already wrote that &lt;a href=&quot;/en/the_end_of_coding_wrong_question/&quot;&gt;this is wrong perspective&lt;/a&gt;. I won’t repeat that, but I want to say that…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Vibing isn’t new and isn’t always an issue.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’m saying that LLM tools are an appraisal for ignorance. The more ignorant we are of the topic we’re working with, the better we see the outcomes. And that, by itself, is not always bad, as there’s &lt;a href=&quot;/en/power_of_ignorance/&quot;&gt;power in ignorance&lt;/a&gt; if we focus on getting it done with the simplest tools we have.&lt;/p&gt;
&lt;p&gt;Still, this can be terrible if we fall in love too much with what we’ve vibed.&lt;/p&gt;
&lt;p&gt;To understand why that “weekend beer” energy is both a superpower and a liability, we need to look at the OODA Loop.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9cf5739c0afb59003ac9bc08a17d8ee9/7032b/2026-04-26-ooda-loop.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACZElEQVR42iVSSW/aYBD1P+2tPVRqD63aQ6RUTaUkqtRUSXtoL1VT0pYoENZspkACJIAxOxjj2MZ4wQveMWDMkn5JnmakN6MZaebNQHcPWCwWDMMgCJLP50mKesitABbLFWCe53UJInd9Xa1WV8ulO3M5mZvOptDQtBhZAhXlctkHcOCrVCog1J3JwBylcQHuSKZl5bJXoXDo5OzMHo2wfnfj6Hu7T0HmyE7dFHd/HCQzuWKhAEzRTU6zCVHlNKvcU95GGm9ibYoTG/VGk2KvKkinx6KVmqYbkG2Pt/YDr7d3t38FcZqRVIVXNV4fTedglXsHU3y77q3FWnfLBa8IzVtioOg8xzqOA8ENmOYlc+TgfU43jFZf2IOrjKh4MzeTzaYvLyeTiaqpL0MtSrZimWQGRbNXlzAMy7IMNbk60MMwjYk7NVTlolD/mMBPouH3Gxs7O5+CweDm5lY0dLweb500WEmSetwAjF+u1biBCM29eQktxeNxUQSyrVjV+nCOqeoQiM/xAkX3+iyr6dpzP1LrgV4xj6LpXK7RbCO1OqSZ5r9EIhwOkSRpjBxJHKxHqvsl7uFWd8vV/akCNeFVpAMSLQzHb7skT+dLyPEpDJE8FU5HkkjqppO9KCFrez8PUuiL4+a7806qzeVJ+XOaePKnnuu0eyLjSyCFaiudzQRj0cPoKTTU1TbVxSiqSRA4TdOsIKuqpOlfU+2nv4vPDqtfMqQ9WUXzIV/Sj7MaQZKBwJHf/xfDMGg8nWqGJQ5VXpJFZejOZsCc8dhzx958PnXdxxdkeIEVBEBs2y4Wi+ARRVn+D2lgKMEKA3qnAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/9cf5739c0afb59003ac9bc08a17d8ee9/507e6/2026-04-26-ooda-loop.webp 200w,
/static/9cf5739c0afb59003ac9bc08a17d8ee9/28a80/2026-04-26-ooda-loop.webp 400w,
/static/9cf5739c0afb59003ac9bc08a17d8ee9/8d2ea/2026-04-26-ooda-loop.webp 800w,
/static/9cf5739c0afb59003ac9bc08a17d8ee9/68fc1/2026-04-26-ooda-loop.webp 1200w,
/static/9cf5739c0afb59003ac9bc08a17d8ee9/5aa59/2026-04-26-ooda-loop.webp 1408w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/9cf5739c0afb59003ac9bc08a17d8ee9/36ca5/2026-04-26-ooda-loop.png 200w,
/static/9cf5739c0afb59003ac9bc08a17d8ee9/a3397/2026-04-26-ooda-loop.png 400w,
/static/9cf5739c0afb59003ac9bc08a17d8ee9/a331c/2026-04-26-ooda-loop.png 800w,
/static/9cf5739c0afb59003ac9bc08a17d8ee9/8537d/2026-04-26-ooda-loop.png 1200w,
/static/9cf5739c0afb59003ac9bc08a17d8ee9/7032b/2026-04-26-ooda-loop.png 1408w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/9cf5739c0afb59003ac9bc08a17d8ee9/a331c/2026-04-26-ooda-loop.png&quot;
            alt=&quot;OODA loop&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Disclaimer, it’s not a competition for Ralph Wiggum Loop. It’s much older and generic.&lt;/p&gt;
&lt;p&gt;Military strategist John Boyd developed the OODA loop (Observe, Orient, Decide, Act) for fighter pilots. In a dogfight, the pilot who cycles through these four stages the fastest and most accurately survives.&lt;/p&gt;
&lt;p&gt;In software, the “dogfight” is the gap between your intent and the production-ready feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OODA loop is built from four steps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Observe&lt;/strong&gt; - This is the intake of raw, unfiltered information. In our world, this means looking at the state of the system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orient&lt;/strong&gt; - This is the most critical and difficult stage. It’s where you filter your observations through your experience, culture, and technical knowledge.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decide&lt;/strong&gt; - Based on your orientation, you formulate a hypothesis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Act&lt;/strong&gt; - You execute.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Getting back to my favourite founder and LLM-based tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The reason founder could build a PoC in a weekend while the team needed more than two weeks is that he bypassed the Observe and Orient phases. He went straight from a vague idea to Act.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If we skip or brush past the observation step, it feels like lightning speed. If the fancy UI grid is there and it does something we wanted, we move on. We’ve outsourced Orientation to our own ego. It’s too easy to assume that because we wrote it, it works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Observation is the intake of raw data.&lt;/strong&gt; In a professional environment, our eyes aren’t enough. We need a Harness. If we don’t have automations,  tests, integration tests, and pristine traces, we aren’t observing the system; we’re just looking at it. If the inputs are messy, our observation is clouded.&lt;/p&gt;
&lt;p&gt;But real engineering, the kind that takes those “two weeks”, is about closing the loop properly. That’s also where we need different perspectives and knowledge sharing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Orientation is where you process those observations.&lt;/strong&gt; This is the part where LLMs make us feel smarter than we are. If we don’t understand how a database handles concurrent connections, our “orientation” of a generated script will be shallow. We’ll see code that “looks” right, decide it’s fine, and act by deploying it.&lt;/p&gt;
&lt;p&gt;The “I still got it” crowd loves the Decide and Act phases because that’s where the visible progress happens. LLM tools have made these phases nearly instantaneous. We can decide to build a feature and have the code for it in ten seconds.&lt;/p&gt;
&lt;p&gt;The problem is that the faster we Act, the faster we need to Observe. If our “Act” phase takes seconds but our “Observe” phase requires a manual weekend of clicking around and drinking beer, our OODA loop is broken. We’re just generating a pile of stuff that we haven’t actually verified.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why the team usually needs more than an imaginary “two weeks”.&lt;/strong&gt; They are not “fine-tuning” the single-brilliant-dude masterpiece. They are building the infrastructure required to make the OODA loop sustainable.&lt;/p&gt;
&lt;p&gt;And to make that possible, they need to run the full loop: Observe, Orient, Decide, Act. And do it multiple times. That takes time, but it’s required to assess the direction, automate what needs to be automated, and ensure they can iterate further and run this loop sustainably. That’s critical for delivering the outcome at the expected pace.&lt;/p&gt;
&lt;p&gt;Of course, there’s a danger here, overfocusing on the Orient and Decide can lead to overengineering, building stuff we don’t need. That’s where ignorance can be blissful, especially when we connect it with humility. Being humble about what we don’t know and trying things the easiest way, then learning and making enhancements. Still, humility fails under deadline pressure. The harness doesn’t.&lt;/p&gt;
&lt;p&gt;Let me give you…&lt;/p&gt;
&lt;h2 id=&quot;the-example&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-example&quot; aria-label=&quot;the example permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The example&lt;/h2&gt;
&lt;p&gt;I’m adding proper Observability and Open Telemetry to &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; right now. I spent some time working on it and instrumented the first component: &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html#command-handling&quot;&gt;Command Handling&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course, I had tests to prove it works, but I don’t trust them enough, and I wanted to try it on a real sample, since you never know until you run it. Even the best test suite won’t tell you all.&lt;/p&gt;
&lt;p&gt;So I decided to plug it into the &lt;a href=&quot;https://github.com/event-driven-io/emmett/tree/main/samples/webApi/expressjs-with-postgresql&quot;&gt;sample&lt;/a&gt;. See if it works, how ergonomic the API is and how it fits conventions in this area.&lt;/p&gt;
&lt;p&gt;To do it, I decided to use &lt;a href=&quot;https://grafana.com/&quot;&gt;Grafana stack&lt;/a&gt; and set it up with Docker Compose. So, stable, boring stack. Not going to lie, I vibed the config. Not that there are no docs, but I intentionally wanted to see the typical config people use.&lt;/p&gt;
&lt;p&gt;If someone says LLM-based tools are great at proof of concepts, they don’t run the stuff they vibed. If I made the observation based on the initial config, then an oriented decision would be that it won’t work. Of course, then I did the typical back-and-forth, with the LLM tool doing some Linux command Voodoo to make it work. Once. Then, if you try to repeat it, you won’t know how to do it without doing Voodoo again.&lt;/p&gt;
&lt;p&gt;Again, that’s not much different from the other stuff we do. I’m sure that you had multiple cases, when someone didn’t use Continuous Deployment tools, but clicked through Azure, AWS, GCP portal, deployed the stack, and then there was no trace on how to set it up again (e.g. to have a different environment for testing or demos for customers).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, we need a harness, we need a leash to keep our process on track.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;How to do the harness? My advice is to start simple. We may ask LLMs to give us shell scripts, and we may ask them to run them multiple times. We also need experience and knowledge of what we want to achieve and the tools we use. It’s fine not to remember all the YAML config to set up the Grafana stack, but it’s not fine not to understand why you even use it, how it relates, and how to set it up.&lt;/p&gt;
&lt;p&gt;Still, our first loop can close on the first working solution, even a manually vibed one. But that’s not even a PoC. We need to automate them.&lt;/p&gt;
&lt;p&gt;I asked LLM to take notes on what issues it had, and it solved them. Then, based on that, I asked to research how to code it in TypeScript. And to use tools I know, used in past, validating if there are no new more modern ones. For instance, I was a big fan of &lt;a href=&quot;https://gulpjs.com/&quot;&gt;Gulp.js&lt;/a&gt; and &lt;a href=&quot;https://github.com/adamralph/bullseye&quot;&gt;Bullseye&lt;/a&gt; in the past, but they’re mostly dead. I wanted to have something in the same spirit, using native, maintained tooling.&lt;/p&gt;
&lt;p&gt;I ended up with the following tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sindresorhus/execa&quot;&gt;execa&lt;/a&gt; for running shell scripts,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;native fetch&lt;/a&gt; for calling http endpoints,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://nodejs.org/api/test.html&quot;&gt;native Node.js test tools&lt;/a&gt; for checking if the stack works as expected.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then I asked it to create the script to automate the shell Voodoo they did to make Grafana stack and Docker Compose work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Essentially, it should:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run Docker Compose script starting up services (Grafana, Prometheus, Loki, Tempo, PostgreSQL, etc.).&lt;/li&gt;
&lt;li&gt;Wait for them to check when they’re ready (it usually takes some time).&lt;/li&gt;
&lt;li&gt;Start the application and make a request.&lt;/li&gt;
&lt;li&gt;Check if the predefined dashboard with Emmett metrics appears, and shows expected traces and metrics.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Initial diagnostic tools looked like that&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchWithDiag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; init&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestInit&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; init&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; body &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;(could not read body)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;\n  ✗ &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;label&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; → HTTP &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;\n  body: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;body&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;diagnoseCollector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;otelCollectorMetrics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;unreachable&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; emmett &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\n&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;l&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; l&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;emmett_&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;l&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    emmett&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;\n  collector /metrics (emmett lines):\n  &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;emmett&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\n  &apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;\n  collector /metrics: no emmett_* lines found&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;diagnosePrometheus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; json &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prometheus&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/api/v1/label/__name__/values&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; emmett &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;n&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; n&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;emmett_&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    emmett&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;\n  Prometheus emmett_* metrics: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;emmett&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;, &apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;\n  Prometheus: no emmett_* metrics found yet&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;diagnoseLoki&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; labels &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loki&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/loki/api/v1/labels&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;\n  Loki labels: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;labels&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;, &apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;(none)&apos;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;diagnoseDockerLogs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;service&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; lines &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stdout &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execa&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;docker&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;COMPOSE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;logs&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;--tail&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lines&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    service&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stdout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;(could not get logs)&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;\n  docker logs &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;service&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; (last &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;lines&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;):\n  &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;stdout&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\n&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\n  &apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Are they pretty? No. Can they be improved? Yes. Do they have to be improved at this specific moment? No.&lt;/p&gt;
&lt;p&gt;The setup uses test infrastructure&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CLEANUP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;CLEANUP&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;CLEANUP&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;true&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CLEANUP_AFTER&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;CLEANUP_AFTER&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;CLEANUP_AFTER&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;true&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NO_START&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;NO_START&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;NO_START&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;true&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// ─── configuration ───────────────────────────────────────────────────────────&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;COMPOSE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;compose&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;-f&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;docker-compose.yml&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;--profile&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;observability&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  app&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;http://localhost:3000&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  prometheus&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;http://localhost:9090&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  tempo&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;http://localhost:3200&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  loki&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;http://localhost:3100&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  grafana&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;http://localhost:3001&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  otelCollectorMetrics&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;http://localhost:8889/metrics&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Fresh client per run — avoids stale cart state from previous runs.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SERVICE_NAME&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;expressjs-with-postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CLIENT_ID&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CART_ENDPOINT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;app&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/clients/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CLIENT_ID&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/shopping-carts/current/product-items&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CONFIRM_ENDPOINT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;app&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/clients/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CLIENT_ID&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/shopping-carts/current/confirm&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Matches the .http file — unitPrice is resolved server-side.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ADD_PRODUCT_BODY&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;stringify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;\n▶ client ID for this run: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CLIENT_ID&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;NO_START&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ --no-start: skipping docker compose and app startup&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CLEANUP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ --cleanup: killing port 3000 and tearing down stack (down -v)…&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execa&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;bash&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;-c&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;fuser -k 3000/tcp 2&gt;/dev/null || true&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execa&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;docker&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;COMPOSE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;down&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;-v&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;--remove-orphans&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      stdio&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;inherit&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stackReady &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prometheus&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/-/ready&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stackReady&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ observability stack already up — skipping docker compose up&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ starting observability stack…&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execa&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;docker&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;COMPOSE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;up&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;-d&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stdio&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;inherit&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ waiting for backends…&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;waitFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Prometheus&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prometheus&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/-/ready&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    timeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Prometheus&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;waitFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Grafana&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grafana&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/api/health&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    timeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Grafana&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;waitFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Tempo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tempo&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/ready&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    timeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Tempo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;waitFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Loki&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loki&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/ready&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    timeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Loki&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// /health returns { status: &apos;ok&apos;, service: &apos;expressjs-with-postgresql&apos; } —&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// checking service name lets us distinguish our app from other processes on :3000.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;checkOurApp&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;checkUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;app /health&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;app&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/health&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; json &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; service&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;service &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SERVICE_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;    app /health: service=&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;json&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;service &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;(missing)&apos;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;, expected=&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SERVICE_NAME&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appIsOurs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stackReady &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkOurApp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;appIsOurs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ app already running and healthy — skipping npm start&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; portTaken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;portTaken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Port is occupied but not by our app — stale process or unrelated service.&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;\n  ✗ Port 3000 is occupied by a process that is not this app.\n&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&apos;  It may be a stale version of this app (connected to a wiped database)\n&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&apos;  or a completely different service.\n&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&apos;  Fix: run  npm run verify:observability:cleanup  to kill it and restart,\n&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
          &lt;span class=&quot;token string&quot;&gt;&apos;  or manually free port 3000.\n&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ starting app…&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execa&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;npm&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;start&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stdio&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;inherit&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;waitFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;checkOurApp&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; timeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;app /health&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ setup complete\n&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you see, nothing fancy, the cleanup is even simpler&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token function&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;app&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\n▶ stopping app…&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;kill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;SIGTERM&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ app stopped&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CLEANUP_AFTER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ tearing down stack (down -v)…&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;execa&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;docker&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;COMPOSE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;down&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;-v&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;--remove-orphans&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      stdio&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;inherit&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ stack torn down&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ stack is still running&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;▶ to clean up: npm run verify:observability:cleanup&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Having that we can run tests:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;
&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;successful command returns x-trace-id header&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchWithDiag&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;POST add product&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CART_ENDPOINT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;application/json&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ADD_PRODUCT_BODY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;204&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Expected 204 — body logged above&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; header &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;x-trace-id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;header&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;  ✗ x-trace-id missing — verify the wrapper app in src/index.ts &apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;adds it via @opentelemetry/api before mounting the emmett app&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;header&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;x-trace-id header missing&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;header&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;^[0-9a-f]{32}$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;header&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; is not a 32-hex trace ID&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  traceId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; header&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;  trace ID: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;traceId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;OTel collector exposes Emmett metrics on port 8889&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Send a few more requests so metrics are definitely recorded.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;CART_ENDPOINT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      method&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;POST&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      headers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;application/json&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      body&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ADD_PRODUCT_BODY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;waitFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; text&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;URLS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;otelCollectorMetrics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          text &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;    collector :8889: connection refused&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; emmettLines &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\n&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;l&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; l&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;emmett_&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;l&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;emmettLines&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; allFamilies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;text&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;\n&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;l&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;l&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;#&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; l&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;l&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; l&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;{&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;    collector :8889: no emmett_* metrics yet. Present: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;allFamilies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;, &apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;(none)&apos;&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; timeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;90_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; interval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; label&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;emmett metrics on collector :8889&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;diagnoseCollector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;diagnoseDockerLogs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;otel-collector&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I put it into a &lt;a href=&quot;https://github.com/event-driven-io/emmett/blob/a937ff98ba39d3e504540886d8cd918843b28149/samples/webApi/expressjs-with-postgresql/src/observability.spec.ts&quot;&gt;single file&lt;/a&gt; that can be run as a regular Node.js script.&lt;/p&gt;
&lt;p&gt;It already showed me (and Claude) that what they initially did wasn’t working if you try to run it multiple times. It also showed that doing a full cleanup and rebuild, and making it reproducible, needs more work.&lt;/p&gt;
&lt;p&gt;Is it done? Not yet; it takes too much time and resources to run it continuously throughout the pipeline. The code is a bit messy, so it needs to be organised. It’s segmented into blocks, includes basic automation and tests, and has already gone through some failures to get it done.&lt;/p&gt;
&lt;p&gt;Could I do it better? Sure, but that’s not the point. I wanted to show you my findings during weekend vibing (without beer tho),the real, not polished iteration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The main idea behind OODA loops is not to be perfect, but to iterate quickly, gather feedback as soon as possible, learn from it, develop another theory, and verify it through action.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It’s not about vibing, but it’s also not about analysis paralysis.&lt;/p&gt;
&lt;p&gt;I hope you’re now better equipped to think about when vibing, with beer or without, with LLMs or without, actually helps, and when it doesn’t.&lt;/p&gt;
&lt;p&gt;Vibe coding is just high-frequency steering. It only works if you have a Harness: a mechanical way to observe and orient, so you don’t steer the whole project into a wall.&lt;/p&gt;
&lt;p&gt;Act takes seconds now. Observe takes as long as it always did. Without a harness, you’re not going faster; you’re just making more stuff you haven’t checked.&lt;/p&gt;
&lt;p&gt;Harness is not magic, a new discipline, or the next buzzword; I hope I showed you that a bit in this article on what it may look like.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So iterate fast, but wisely remembering to do the full loop.&lt;/strong&gt; It’s great that LLMs can help us make Acting faster, but we should not skip other steps. We should aim for a fast feedback loop to iterate in the right direction and achieve continuous improvement, to deliver proper value.&lt;/p&gt;
&lt;p&gt;Just like Vibing isn’t new, we shouldn’t abandon &lt;em&gt;”old”&lt;/em&gt; engineering practices. We should also not replace collaboration with solitary self-high fives.&lt;/p&gt;
&lt;p&gt;Check also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/335&quot;&gt;Emmett Pull Request with mentioned changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/interactive_rubber_ducking_with_gen_ai/&quot;&gt;Interactive Rubber Ducking with GenAI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/the_end_of_coding_wrong_question/&quot;&gt;The End of Coding? Wrong Question&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/tricks_on_how_to_set_up_related_docker_images/&quot;&gt;A few tricks on how to set up related Docker images with docker-compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/docker_compose_profiles/&quot;&gt;Docker Compose Profiles, one the most useful and underrated features&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Yoda Principle for better integrations]]></title><description><![CDATA[Try not. Do. Or do not. There is no try! I’m calling this the Yoda Principle. Master Yoda said that to Luke Skywalker a long time ago in a…]]></description><link>https://event-driven.io/en/yoda_principle_in_command_design/</link><guid isPermaLink="false">https://event-driven.io/en/yoda_principle_in_command_design/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b559c6d70cdf5430d251306c8b61d60b/b09c1/2026-04-20-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADZUlEQVR42h2T7U+aVxjG+bRpRXjgQRDkTQRBeKggDEEUEObDO6IVUSm1vs0oq85oTV9Mt65bljm7bl/bJVu2JWvWLvuyZMm27F/77cwPV+6T+5xc57rOdR+NbLFhsTkxma0MD9sZG/WjTEwSDk0RiQRJp2/idDrwjwVEX2EyGGF9qcbeZoulqkolM0fC58UtyyxHvWhmUjnyuTLNxiqNWkvUNp2NHUrFJSKTSWaSNympce7e6dDb3ebiQY/ff7vi9S9f8vz5CfcOb1FfmEbSawk6h9BUq022NvfZ2Tpkb+eID3Y/Ynurx/7eMY16h+JClnxG4eSow9XlEW8F2d9/fcfPP33F/bOuEJEkkwnjdpjR3uhDU19cp764Rq5QYSarMpNRicbTpObmMTvGCIQn2Fhb4LjX4vjDNmvtAhePt3n29IDzs9u0VzLkBGE6EcQmS2j0Jis3JBP9kpE+SeIdnQ6n149r3I/WIKOVDGTmolx+ccBSI8tmt8xyM0OxEKOzXmA65iWdCpATZxxmYXnAYEBrNDBolNHJJnSmIbSi6mUzepOFfr0kAvJysFPlmxenPH60Qy47SaddJhkPEfTbiUc91KrCelJBo5OHkMw2DJaRayKtIB68hlhLMum5GT5+csLaSpbz800ODldFSDFhNStUhUkmfNekoQkX1XIKjdsnxsIbxiXg8EwwNOIWyoZxjHp4cnHKv/+84e2bV5RFkup8lHDYQyziZXpKIO4joriZ8DsY91gJBexowtE5QpE0ASVBIBS/rp7xKFdXn/HnHz/y6uUlnz87x2aWCQddBHx2IiE3YUHiHbXisptQFD+qmhVPICynZsskUiqxRJ6pRBZlMsX7CxV+ff2SH77/lrPTfS4eHTNmtzFsNaAI0qDfiRJw4nZZMEo6ktPvUSypzCan0RRKq+QFsoWmQIN4okDv8JAXXz9lb7dL9/YqZ+c9MokYg33v4vWN4BMq7WLu9AP9uK02qrUS6kKeWqWIplZdFo0W1foGlcUu88U2945O+PST++JQjnq9xP7+XW41K9jFSJmNeozGQSwGPSMDWmYTcRqNCkW1QKWs/v9TGiLyBvVaU2y0qFTbPHz4QCR7SmulwWw6yZ3uGhudFnGfD5/BiF/Mm2IZJuRwXpOU1Ly4fJ5CLsN/6OTExkMFIo8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/b559c6d70cdf5430d251306c8b61d60b/507e6/2026-04-20-cover.webp 200w,
/static/b559c6d70cdf5430d251306c8b61d60b/28a80/2026-04-20-cover.webp 400w,
/static/b559c6d70cdf5430d251306c8b61d60b/0367d/2026-04-20-cover.webp 626w&quot;
              sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/b559c6d70cdf5430d251306c8b61d60b/36ca5/2026-04-20-cover.png 200w,
/static/b559c6d70cdf5430d251306c8b61d60b/a3397/2026-04-20-cover.png 400w,
/static/b559c6d70cdf5430d251306c8b61d60b/b09c1/2026-04-20-cover.png 626w&quot;
            sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/b559c6d70cdf5430d251306c8b61d60b/b09c1/2026-04-20-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Try not. Do. Or do not. There is no try!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’m calling this the Yoda Principle.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=BQ4yd2W50No&quot;&gt;Master Yoda said that to Luke Skywalker a long time ago in a galaxy far, far away&lt;/a&gt;. He was teaching Luke how to name commands properly while trying to untangle some legacy enterprise mess.&lt;/p&gt;
&lt;p&gt;I’m sure you’ve also seen a death star of weirdly-named stuff. Some of them have already tripped you hours of thinking whether someone named this thing badly, or there’s some hidden truth behind it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let’s discuss that by the example: E-Commerce order fulfilment.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The order is placed automatically once the customer confirms the items in the shopping cart. We’re not making any product reservations before the shopping cart is confirmed, as this would lock it for other customers, and, as you know, they tend to drop items from their carts.&lt;/p&gt;
&lt;p&gt;Once we receive the event notification that the cart has been confirmed, we’ll start the order fulfilment process. It starts (as mentioned) with the order initiation, which acknowledges and initiates the multi-step fulfilment process.&lt;/p&gt;
&lt;p&gt;The first step is checking product availability before confirming the order. We need to determine whether we can proceed with completing the shipment and initiating product payment. If the product is unavailable, we need to either ask the customer to wait until we have it again or cancel the order.&lt;/p&gt;
&lt;p&gt;We have dedicated modules for order fulfilment and for inventory. Fulfilment is the orchestrator, and inventory is responsible for tracking the state in warehouses.&lt;/p&gt;
&lt;p&gt;The ordering module would need to call the inventory module to verify product availability. We could send a command (through the messaging system or web api). How would we name it?&lt;/p&gt;
&lt;p&gt;What about &lt;strong&gt;&lt;em&gt;VerifyProductExists&lt;/em&gt;&lt;/strong&gt;? We’d send the product id and quantity from the order information, and return true if we have enough products, false otherwise. Sounds fair, right?&lt;/p&gt;
&lt;p&gt;Well, it may seem nice at first glance, but what happens if more than one order verifies the same product availability, and we’re running short?&lt;/p&gt;
&lt;p&gt;Then we’re vulnerable to race conditions I described in &lt;a href=&quot;/en/tell_dont_ask_how_to_keep_an_eye_on_boiling_milk/&quot;&gt;Tell, don’t ask! Or, how to keep an eye on boiling milk&lt;/a&gt;. The information we get is only valid at the time of querying. If we don’t lock the product quantity, it can change before we get a response (think: Black Friday-like demand).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Naming our command like &lt;em&gt;VerifyProductExists&lt;/em&gt; is a mistake&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;VerifyProductExists&lt;/em&gt; is not even a command; it’s a query. Command is a request (intention) to run business logic. Query is a request to return data.&lt;/p&gt;
&lt;p&gt;Of course, pragmatically, our &lt;a href=&quot;/en/can_command_return_a_value/&quot;&gt;command can return status information about the result of our operation&lt;/a&gt;. But the intention is different.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s our real intention here?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The real intention is that we’d like to reserve products so we can ship them and get payment for them, not to verify if they exist. It’d be better to name our command as ReserveProducts or LockProducts.&lt;/p&gt;
&lt;p&gt;Why does it matter?&lt;/p&gt;
&lt;p&gt;If we’re naming our commands with Verify/Validate/Check prefixes, we’re putting ourselves into the wrong mindset. We’re not focused on actions and integrations, but just brief checking. If we’re in such a mode, it’s easy to handwave the integration complexity.&lt;/p&gt;
&lt;p&gt;Locking for shipment may require sending someone to double-check that the product is in the warehouse and hasn’t been stolen, damaged, or other steps. It may be an async process on its own. Still from the ordering module, we should not care, as we’re telling what our intention is and expect to get events informing us whether the reservation succeeded, failed, or timed out (we don’t want to lock products forever in case of order fulfilment issues, but only for some time).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prefixes like Verify/Validate/Check, etc., are just synonyms for trying.&lt;/strong&gt; And well, commands are always a form of trying. The handling module can reject the command, as its business rules and state are the source of truth.&lt;/p&gt;
&lt;p&gt;We should always assume that the command processing can fail. We should not be discouraged by that, and we should double-check everything. We should not be intimidated by the potential failure, but prepared for it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We should try not. Do or do not. There is no try.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What if we have both? So &lt;em&gt;VerifyProductExists&lt;/em&gt; and &lt;em&gt;LockProducts&lt;/em&gt;? It can work if the first one is a query used by the Shopping Cart module, without any guarantee that the data isn’t stale, on a best-effort basis.&lt;/p&gt;
&lt;p&gt;If we’re always requiring VerifyProductExists from the handling module before LockProducts, we’re making our communication chatty. I described that in &lt;a href=&quot;/en/what_does_mr_bean_opening_the_car_have_to_do_with_programming/&quot;&gt;What does Mr Bean opening the car have to do with programming?&lt;/a&gt; that this is not only a bad developer experience, but also just redundancy. Locking should already verify whether the product exists, so why require someone to memorise those scenarios instead of checking it internally?&lt;/p&gt;
&lt;p&gt;The same goes for cases like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;verify payment has been made,&lt;/li&gt;
&lt;li&gt;check if the order wasn’t already fulfilled,&lt;/li&gt;
&lt;li&gt;validate if the shipment has been completed,&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of them either hide a missing business concept or should be a business rule verified within the specific action (e.g., confirming an order).&lt;/p&gt;
&lt;p&gt;I recognise this may seem nitpicky, but big things are built on small details.&lt;/p&gt;
&lt;p&gt;If we don’t think about such things, we’ll not only end up with misnamed integrations but also fight &lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;race conditions&lt;/a&gt; and incorrect boundaries.&lt;/p&gt;
&lt;p&gt;Then &lt;a href=&quot;https://www.youtube.com/watch?v=cTwZZz0HV8I&quot;&gt;we’ll be doomed&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So better think twice and do or do not.&lt;/p&gt;
&lt;p&gt;May the force be with you!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Anti-patterns in event modelling - Passive-Aggressive Events]]></title><description><![CDATA[Have you ever heard phrases like. Just an update, the milk ran out. Someone finished it and put the empty carton back. Or So everyone is…]]></description><link>https://event-driven.io/en/passive_aggressive_events/</link><guid isPermaLink="false">https://event-driven.io/en/passive_aggressive_events/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ac3e8b12fe2538f089a41dbaca33a90f/7032b/2026-04-13-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACqklEQVR42gGfAmD9AE9DL8TDutPUzeTh2cG7rI2Lei0sGkk1GkguDkMrDGRBGWlFHGVBGWE/F1MzDkotCzIhCU85JGVMNllDLQBHPCfCv7bW2tf////q8O2ino4fIBI6NCNQNBFNMhCFYTqFXzV4UShELxI+LxZRPyYtJhdUPyhqTjVSPioAQjMZuLir2+De8vXu5ezhmpOCJyMWZ088UTMQWz0ZooBclHBHiWI3PTMiIiMaYWFVam5qZlI8YT8gVkApAHloSrS9pZOlkrvFs7zJqZiVhZFuVYZjTEEvFD4sFHllS4VsTXxmR5R0W2NGMWxpXIiLiHBkU15KMFJDLwClopTCycKYoY+nqpiPkYVzeXdLST8pLSMbIRleVUV4b197fHCWmpGaiXY1JhYXJCRka2lhYllNSz5ZVEcAZ2pifH98fH55gYR+d3lyY2pqMDg0MTYwZVlMnZB+Rk1JDhgXMDcyQkpGCRgcAA8UEhwbR0pEPzkpRkAyAB8pJVRfXz1FQkZPTXZ9em51dU9UUEZMR2FMN2BFKWFJKmFKK1BEMDE4ORAlMAcUFgAGBDcwIU0+KDYwIgAxNTAwNi9CR0BKTUQ5PTZVXVwtMiotMihQTT9PNRt+ZEh0Y0xXPSR2X0wMHyYCCggABQQoJRp4XDtMOSAAPR8AJBUCPT0ydHdvcndyX2Jca2hbeWFCkm1FgnJdi3liZ2JWaVQ5aEklDxQOAAUDAA0NCxENSy8QQTIcAFJCLH6BeZmgnHh4b6uml4uAbjs7MDs1KHVzaYuIfpyJb5OHdGJVQhwUBgIEAAIMDAIPDwALCTsmCzkuGgCNe2Owp5WkmohMQi9nTS2hhGFwYkxkVT1+b1qMakN+Vio+LBMEBAAAAQABCAgDEhYBDAsNDgU5Jw0uKhuYIdajyUw5hgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/ac3e8b12fe2538f089a41dbaca33a90f/507e6/2026-04-13-cover.webp 200w,
/static/ac3e8b12fe2538f089a41dbaca33a90f/28a80/2026-04-13-cover.webp 400w,
/static/ac3e8b12fe2538f089a41dbaca33a90f/8d2ea/2026-04-13-cover.webp 800w,
/static/ac3e8b12fe2538f089a41dbaca33a90f/68fc1/2026-04-13-cover.webp 1200w,
/static/ac3e8b12fe2538f089a41dbaca33a90f/5aa59/2026-04-13-cover.webp 1408w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/ac3e8b12fe2538f089a41dbaca33a90f/36ca5/2026-04-13-cover.png 200w,
/static/ac3e8b12fe2538f089a41dbaca33a90f/a3397/2026-04-13-cover.png 400w,
/static/ac3e8b12fe2538f089a41dbaca33a90f/a331c/2026-04-13-cover.png 800w,
/static/ac3e8b12fe2538f089a41dbaca33a90f/8537d/2026-04-13-cover.png 1200w,
/static/ac3e8b12fe2538f089a41dbaca33a90f/7032b/2026-04-13-cover.png 1408w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/ac3e8b12fe2538f089a41dbaca33a90f/a331c/2026-04-13-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Have you ever heard phrases like.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Just an update, the milk ran out. Someone finished it and put the empty carton back.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;So everyone is aware, the meeting started 15 minutes ago.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Heads up: the coffee machine is empty again.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s fine, I already walked the dog.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’m sure you either heard or used such phrases.&lt;/p&gt;
&lt;p&gt;We all know that there’s some intention behind it.&lt;/p&gt;
&lt;p&gt;The intention is not to inform, but to trigger some action.&lt;/p&gt;
&lt;p&gt;Formally, we’re reporting on events to announce the facts, but in practice, they’re passive-aggressive words. The real intention is to command someone.&lt;/p&gt;
&lt;p&gt;We don’t want to inform you that the trash bin is full, but we want someone to take it out. We don’t want to inform you that the coffee machine requires a coffee bean refill, but we want someone to do it.&lt;/p&gt;
&lt;p&gt;Passive-aggressive tone is the worst. It’s toxic for both sides of the communication. Usually, it’s just better to ask someone to do it.&lt;/p&gt;
&lt;p&gt;The same rule also works in Event-Driven Modelling. We should avoid passive-aggressive communication at all costs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We should watch out for Passive-Agressive Events. So events that should be commands.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I already warned you in past: &lt;a href=&quot;/en/dont_let_event_driven_architecture_buzzwords_fool_you/&quot;&gt;don’t let Event-Driven Architecture buzzwords fool you&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Event-Driven Architecture is an integration architecture style. We’re trying to model our business processes so they run smoothly. For that, we prefer a non-blocking communication flow, with things happening in parallel at their own pace. The goal is to achieve autonomous components that reduce the time needed to understand them. That helps &lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;maintain, or even replace them&lt;/a&gt; as your business evolves.&lt;/p&gt;
&lt;p&gt;And events are enablers for that. They notify of what has happened, allowing other components to interpret facts and take the next steps.&lt;/p&gt;
&lt;p&gt;But… Let me show one more photo.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7601e4c1d83592534cd7476672138d7d/6c738/2026-04-13-parliament.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIDAQX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAP/aAAwDAQACEAMQAAAB52Kg2Ikf/8QAFxABAQEBAAAAAAAAAAAAAAAAAAECEf/aAAgBAQABBQJ1zTarbH//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAXEAEAAwAAAAAAAAAAAAAAAAABABAh/9oACAEBAAY/AqwYVjP/xAAbEAACAwEBAQAAAAAAAAAAAAABEQAhMVFxkf/aAAgBAQABPyFkCt45ZlVTuMsgezZd2ET9h1EAn//aAAwDAQACAAMAAAAQOD//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAWEQEBAQAAAAAAAAAAAAAAAAAAARH/2gAIAQIBAT8QZH//xAAbEAEBAAMBAQEAAAAAAAAAAAABEQAhMUGRof/aAAgBAQABPxAYK5RDmDhEUznyfmC1YdSp8wkwCXQlwxGbrRN5o+wwfXP/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/7601e4c1d83592534cd7476672138d7d/507e6/2026-04-13-parliament.webp 200w,
/static/7601e4c1d83592534cd7476672138d7d/28a80/2026-04-13-parliament.webp 400w,
/static/7601e4c1d83592534cd7476672138d7d/8d2ea/2026-04-13-parliament.webp 800w,
/static/7601e4c1d83592534cd7476672138d7d/68fc1/2026-04-13-parliament.webp 1200w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/7601e4c1d83592534cd7476672138d7d/37402/2026-04-13-parliament.jpg 200w,
/static/7601e4c1d83592534cd7476672138d7d/4cda9/2026-04-13-parliament.jpg 400w,
/static/7601e4c1d83592534cd7476672138d7d/c60e9/2026-04-13-parliament.jpg 800w,
/static/7601e4c1d83592534cd7476672138d7d/6c738/2026-04-13-parliament.jpg 1200w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/7601e4c1d83592534cd7476672138d7d/c60e9/2026-04-13-parliament.jpg&quot;
            alt=&quot;parliament&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;It’s parliament, per the official definition: a room full of angry, shouting people.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If we model our communication only in terms of events, our system will look just like that.&lt;/strong&gt; We’d just announce new facts in a passive-aggressive style and not be interested in what happens next. Oh, wait, are we really not interested? Actually, we are. If someone won’t do what we expect with our information, we’ll be even angrier.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/en/whats_the_difference_between_event_and_command/&quot;&gt;What’s the difference between a command and an event?&lt;/a&gt; Both are messages. They convey specific information: a command indicating intent to do something, or an event describing what happened. From the computer’s point of view, they are no different. Only the business logic and the interpretation of the message can distinguish between an event and a command.&lt;/p&gt;
&lt;p&gt;And that’s the main difference: commands can be rejected by the command handler. Events can only be ignored.&lt;/p&gt;
&lt;p&gt;If we send an event, we expect someone to be interested, but we don’t know who or how many components will. We just inform.&lt;/p&gt;
&lt;p&gt;This can easily change into passive-aggressive:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I did my work, now it’s your turn.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And here’s the crucial moment. If we’ll always have a single consumer for an event that needs to run the specific logic and expect to get the particular event back, then it should be a command. It’s not an event, we don’t inform. We just want someone to take the next specific step and let us know when they finish.&lt;/p&gt;
&lt;p&gt;But hey, aren’t we making our communication synchronous?&lt;/p&gt;
&lt;p&gt;What does it even mean, synchronous or asynchronous? That’s what &lt;a href=&quot;https://www.youtube.com/watch?v=2LMEJ-WGFTk&quot;&gt;Sam Newman discussed in his great talk&lt;/a&gt;. The main conclusion is that synchronous vs asynchronous discussion is actually about blocking or non-blocking. And that’s much broader than the technicalities of whether we call something in-process via an HTTP endpoint or a messaging system.&lt;/p&gt;
&lt;p&gt;It’s a common misconception that events are published through a messaging system (e.g. Kafka, RabbitMQ, SQS, WhateverQueue) and commands are sent through WebAPI.&lt;/p&gt;
&lt;p&gt;Those are technicalities. As said, both events and commands are messages; we can send them through a messaging system or via HTTP (e.g., events via webhooks).&lt;/p&gt;
&lt;p&gt;This misleading split stemmed from our expectation about handling, so we expected the command to wait for the result. For events, we don’t expect a specific result, at least in theory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If we publish a specific event to the messaging system and expect a specific critical path of follow-up events, then we’re not making our communication non-blocking. It’s still sequential.&lt;/strong&gt; We cannot proceed until the expected sequence occurs.&lt;/p&gt;
&lt;p&gt;Whether something is blocking or not is not established by the tools we use, but by how our business process looks.&lt;/p&gt;
&lt;p&gt;Speaking about it.&lt;/p&gt;
&lt;p&gt;Let’s get back to our favourite E-Commerce Order scenario (read more in &lt;a href=&quot;/en/predictable-identifiers-enabling/&quot;&gt;Predictable Identifiers: Enabling True Module Autonomy in Distributed Systems&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;We could model it so we just publish the &lt;em&gt;OrderConfirmed&lt;/em&gt; event and passively-aggressively expect that others will take it from there. So:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The payment module will initiate the payment.&lt;/li&gt;
&lt;li&gt;Inventory will start completing shipments.&lt;/li&gt;
&lt;li&gt;The notification module will send a confirmation e-mail.&lt;/li&gt;
&lt;li&gt;Fraud detection module will check if the order is not rigged.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once we receive information about a successful shipment or payment registration, we can complete the order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You may notice two paths for order processing:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Blocking&lt;/strong&gt; - We need to wait for information about payments and shipments. This is our critical path.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-blocking&lt;/strong&gt;- Order process shouldn’t stop if the notification wasn’t sent or the data warehouse wasn’t able to process events. We’d like that to happen, but it’s expected rather than critical.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, both payments can fail (if our customer doesn’t have enough money), and the shipment may not be completed (if it’s Black Friday, and multiple people are competing for the same product).&lt;/p&gt;
&lt;p&gt;If that happens, as the ordering module, we also need to take action, for instance, do reimbursement if the shipment wasn’t completed, and eventually cancel the order.&lt;/p&gt;
&lt;p&gt;If we’re just focused on events, we tend to forget about &lt;em&gt;“negative”&lt;/em&gt; scenarios. If all communication is through events, then it’s too easy to stay in I-Alread-Did-My-Job mode.&lt;/p&gt;
&lt;p&gt;We may not notice that another module can actually say no:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Payment module can say: &lt;em&gt;Man, that’s not going to happen, you’ve already run out of money&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Shipment module can say: &lt;em&gt;Man, I’m sorry, but you weren’t fast enough and we’ve run out of product&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And both of those scenarios will block successful order completion.&lt;/p&gt;
&lt;p&gt;If we don’t foresee that and stay in passive-aggressive mode, this will have severe consequences: blocked orders, missed communication, and dissatisfied customers.&lt;/p&gt;
&lt;p&gt;How to find such scenarios? &lt;a href=&quot;/en/intro_to_example_mapping/&quot;&gt;Doing Example Mapping during modelling can be a good option for that&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Most importantly, we need to embrace the fact that some scenarios require direct, blocking communication, and others don’t. Just like in real life, sometimes it’s just more effective to tell someone to do something. We should avoid micromanagement and aim for autonomy, but not end up with anarchy.&lt;/p&gt;
&lt;p&gt;In our case, it’d be better to have a coordinator (&lt;a href=&quot;/en/how_to_have_fun_with_typescript_and_workflow/&quot;&gt;workflow&lt;/a&gt;, &lt;a href=&quot;/en/saga_process_manager_distributed_transactions/&quot;&gt;saga, process manager&lt;/a&gt;, etc.) that publishes the &lt;em&gt;OrderConfirmed&lt;/em&gt; event for modules not on the critical path and sends commands like &lt;em&gt;RecordPayment&lt;/em&gt; and &lt;em&gt;InitiateShipment&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;By that, we’re separating responsibilities and making explicit what should be explicit. This also helps in understanding the business process, as you have a central place to see the critical flow and get proper observability.&lt;/p&gt;
&lt;p&gt;Lacking tracing and observability of the business process is one of the most common issues &lt;a href=&quot;/en/training/&quot;&gt;I see in my clients’ projects&lt;/a&gt;. As said, if we don’t want to end up with parliament instead of proper communication in our system, we need to be explicit about our intention.&lt;/p&gt;
&lt;p&gt;Is that all? Not quite, there’s one more message type we model as events that should not be events.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gregor Hohpe, in &lt;a href=&quot;https://www.enterpriseintegrationpatterns.com/patterns/messaging/Message.html&quot;&gt;“Enterprise Integration Patterns”&lt;/a&gt;, besides &lt;a href=&quot;https://www.enterpriseintegrationpatterns.com/patterns/messaging/EventMessage.html&quot;&gt;Event&lt;/a&gt; and &lt;a href=&quot;https://www.enterpriseintegrationpatterns.com/patterns/messaging/CommandMessage.html&quot;&gt;Command&lt;/a&gt; defines one more message type: &lt;a href=&quot;https://www.enterpriseintegrationpatterns.com/patterns/messaging/DocumentMessage.html&quot;&gt;Document&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What’s the Document? It’s a state. Or to be precise: self-contained data we have at a certain point in time. We can store it, but we can also publish information about its new value.&lt;/p&gt;
&lt;p&gt;That’s probably why Martin Fowler frames it as &lt;a href=&quot;https://martinfowler.com/articles/201701-event-driven.html&quot;&gt;Event-Carried State Transfer&lt;/a&gt;, and I don’t like that term. For me, it’s extremely misleading; it’s not an event, as it doesn’t tell of what has happened, but what has changed.&lt;/p&gt;
&lt;p&gt;It’s a common anti-pattern, a variation of &lt;a href=&quot;/en/state-obsession/&quot;&gt;State Obsession&lt;/a&gt;. Too many people believe it’s fine to connect the messaging system to the database, use tools like &lt;a href=&quot;https://en.wikipedia.org/wiki/Change_data_capture&quot;&gt;Change Data Capture&lt;/a&gt;, and publish it automatically to others.&lt;/p&gt;
&lt;p&gt;Still, we’re ending up again in passive-aggressive communication:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You have all you need. The whole state is in the &lt;em&gt;events&lt;/em&gt;, just interpret it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But how can you reason about what has happened if instead of &lt;em&gt;OrderConfirmed&lt;/em&gt; you get &lt;em&gt;OrderCreated&lt;/em&gt;, &lt;em&gt;OrderUpdated&lt;/em&gt;, &lt;em&gt;OrderDeleted&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;You not only deal with &lt;a href=&quot;/en/clickbait_event/&quot;&gt;Clickbait Events&lt;/a&gt; but also have a leaking business abstraction. All consumers need to understand the internals of your processing to detect a specific type of change. I wrote about it in detail in &lt;a href=&quot;/en/internal_external_events/&quot;&gt;Internal and external events, or how to design event-driven API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Again, the loose coupling of the event-driven processing is only loose for producers; consumers need to adapt. This can lead to hidden coupling, where a change in the producer breaks consumer flows. And that’s the worst type of coupling you can get.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If we’re making commands explicit, we’re also making an explicit relationship between components.&lt;/strong&gt; It’s no longer flattened to producer &amp;#x3C;=&gt; consumer, where the producer always shapes the communication. Now, if the other component exposes a command, that’s the driving force behind its behaviour. This helps to shape autonomy. In our case, we could make the Payment Module a generic module with a stable public API for registering payments, and an ordering module that requests them, in accordance with the Shipment Module. Fraud Detection could continue subscribing to events, as it already does. &lt;a href=&quot;https://github.com/ddd-crew/context-mapping&quot;&gt;Context Mapping&lt;/a&gt; can greatly help in finding those relationships.&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h2&gt;
&lt;p&gt;We tend to be all about events these days, but they’re not the only message types. In our systems, messages take various forms: Events, Commands, and Documents, each serving distinct purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Documents are all about state transitions&lt;/strong&gt;, which are essential for syncing data across services but missing deeper business insights.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commands represent a clear intent to act&lt;/strong&gt;, directed with an expectation of execution, and can be accepted or rejected.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Events are immutable facts&lt;/strong&gt;, announced without waiting for a response. They’re like broadcasting news, hoping it catches the right ears.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7fbe63cf6b9ff88cc94828c5025f409d/58e7d/2026-04-13-message_types.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACXklEQVR42jVRS08TURjtX3BpYuLGvXvYuDEaHxg3RAOJUWPUsFE2xicLiWiipohE2yatoEIaSAP2gW0hfdAWpI2WWN5Q+2CmM53Hnbn3zp0ZWoJ3qiTnfjk3+U7O+b7PBjUDEbNWlze2/nB1uS4ClpcEUWFqQoXhKa8yHCcAUYa00gaWE1lOULGuIN1GHyJ7axtF74QvOpdIphbjyXQ8mZlPU5KZDnx3ez77Q+FAKBoIRSKz8alvoXA0Jkgq1EybpGBZJRAbWG/IkACoY32PF9UyI1Bumk3DbGKNSDKgWUrVWqnCigABqAFILGcVGwqNASkxBQkCGSyvFh/0DY8Fc/544av/Ryi1kfjN7fKQGE1MGrQfqIdiBRnUhH6Q3mR2Wdd710wgZu/t8X7x2Ye8d249utk/fffq9aV4DJsHsqLRgVVkmdlodNQCJcTcL1drmcT865fv2o+fmHQ787GUzz25mFt33OspzMf1/QM6IN2RFZaK5VYACkpUzagwYjEXvnLh4tEjx54/fOroG3C9+lDIpoLjU/mFBWQ0/wW2qkpsh0oLmJiKip7cf3zqZJv99jlH743us5eedZ3POfvLvuGdxTTSDyA0qO3/2JYMtqBiEeoSNDyjEwMvBoPekfWMX66s/EzOluZ88nqeFzig6TLSADqcWUXEOrVmMgK85oh1OtPdI9nUFm+g+orjTHKgfXmwjSsWlqo7HR9Pd41f7vR0/NouYNygN7YlVoXtEsuwnATJWHrTky46E5uFsogUcTM6tDbzdjvyBtSqZanuSjg+Zd2jWU+1zmukoSDyF3+YHVm7hqkAAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/7fbe63cf6b9ff88cc94828c5025f409d/507e6/2026-04-13-message_types.webp 200w,
/static/7fbe63cf6b9ff88cc94828c5025f409d/28a80/2026-04-13-message_types.webp 400w,
/static/7fbe63cf6b9ff88cc94828c5025f409d/8d2ea/2026-04-13-message_types.webp 800w,
/static/7fbe63cf6b9ff88cc94828c5025f409d/353eb/2026-04-13-message_types.webp 1188w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/7fbe63cf6b9ff88cc94828c5025f409d/36ca5/2026-04-13-message_types.png 200w,
/static/7fbe63cf6b9ff88cc94828c5025f409d/a3397/2026-04-13-message_types.png 400w,
/static/7fbe63cf6b9ff88cc94828c5025f409d/a331c/2026-04-13-message_types.png 800w,
/static/7fbe63cf6b9ff88cc94828c5025f409d/58e7d/2026-04-13-message_types.png 1188w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/7fbe63cf6b9ff88cc94828c5025f409d/a331c/2026-04-13-message_types.png&quot;
            alt=&quot;message types&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Event-Driven Architectures enable loose coupling, but only for producers. To make consumers loosely coupled, we need to take extra steps, embrace different message types, and have them participate in modelling business processes.&lt;/p&gt;
&lt;p&gt;If we go too far with an event-all-the-things communication style, we’ll make our system a room full of shouting people, with a passive-aggressive communication style. Or just aggressive.&lt;/p&gt;
&lt;p&gt;In consequence, we won’t know what’s happening in our system, will see only noise, and will have a hard time making it reliable, observable and predictable. We should treat our messages as a communication contract, API and model their flow in a way that shapes our regular communication.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So next time, ask yourself if your event shouldn’t be a command. If it has always had a single consumer and you expect a specific event back, then it’s probably so.&lt;/strong&gt; It’s all about being clear about the intention, not lying to yourself and others.&lt;/p&gt;
&lt;p&gt;I hope this article will equip you with the knowledge to fix that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you’re dealing with such issues, I’m happy to help you through consulting, &lt;a href=&quot;/en/training&quot;&gt;training&lt;/a&gt; or mentoring. &lt;a href=&quot;mailto:oskar@event-driven.io&quot;&gt;Contact me&lt;/a&gt; and we’ll find a way to unblock you!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See also more in series about &lt;a href=&quot;/en/anti-patterns/&quot;&gt;event modelling anti-patterns&lt;/a&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/state-obsession/&quot;&gt;State Obsession&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/property-sourcing/&quot;&gt;Property Sourcing&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/i_will_just_add_one_more_field/&quot;&gt;I’ll just add one more field&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/clickbait_event/&quot;&gt;Clickbait event&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/passive_aggressive_events&quot;&gt;Passive Aggressive Events&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/one_or_more_event_that_is_the_question/&quot;&gt;Should you record multiple events from business logic?&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/on_putting_stream_id_in_event_data/&quot;&gt;Stream ids, event types prefixes and other event data you might not want to slice off&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Check also more general considerations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/events_should_be_as_small_as_possible/&quot;&gt;Events should be as small as possible, right?&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/whats_the_difference_between_event_and_command/&quot;&gt;What’s the difference between a command and an event?&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/internal_external_events/&quot;&gt;Internal and external events, or how to design event-driven API&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/event_streaming_is_not_event_sourcing/&quot;&gt;Event Streaming is not Event Sourcing!&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/dont_let_event_driven_architecture_buzzwords_fool_you/&quot;&gt;Don’t let Event-Driven Architecture buzzwords fool you&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_design_software_architecture_pragmatically/&quot;&gt;How to design software architecture pragmatically&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/gdpr_in_event_driven_architecture/&quot;&gt;How to deal with privacy and GDPR in Event-Driven systems&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The one where Oskar explains Example Mapping]]></title><description><![CDATA[One of the first indications of getting old(er) is when people stop getting your movie or music references. Of course, based on this rule…]]></description><link>https://event-driven.io/en/intro_to_example_mapping/</link><guid isPermaLink="false">https://event-driven.io/en/intro_to_example_mapping/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d6c337c4a5990f5ade81c03cde51d343/2165d/2026-03-30-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 84%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAACYUlEQVR42q1T6WvaQRD1H/VjPvspJUKbFsEUrVBBqmKMBWNriFUptBW1otFEg9dPrWcOvPCKiEYM4vn6my1atYYedGDZmWXn7duZNwI8YvP5nK3VeJu/aQL8hW0+8lvAxeWHhwcUCgXc3Nzg7u4O4/EYjUYDtVqNxdVqFdPpdOsDWwEJSCKRYG9vDzs7OwiHw7BYLBCJRBCLxRAKhQx02/fXAOnV2WyGfr/P2FCSy+VCp9PB7e0t8vk8O49Go4w1/WQ4HK4BC1aDXq8Hs9kMqVQKhUIBjUYDpVIJvV4PLsFBLpdDq9XCZrNBrVbj4EDKSkNGRNYAJ5MJBoMBUqkUY3N1dQWO4xjLZDKJVquFZCoJf8CPaCzKzkulEsshMMpfAl5fX0Mmk0Gn08FgMCAWi+Hk5ATHJhOO9EewWj8gwTNUqVRwOp0wGo2w2+04PDxk9yg+Pz//CdjtduHz+RCJRFCtVNju9/uRyWThcDhweXkJj8fDwLh4HLlcDnF+p4eLxSJOT0/RbrfXm2Li2bxRa/DN68brV3K8PX6HrxdhvHi+j2Ihgmf7TxH48gkBtwsXySx2d5/g88f3cDtsOHgpw4Rv0hJwNBqxhpDWKjxDqhnpLc/Xkbp5f99ldSUr8Yzq/D1i1+t1EQpe8CWx/iqbxyaACr4Q8Ob4bctZAyQd0qKOLfwfibOlP1u5s+r/0yz/iQkWtJvNJoKhIKsN6Y/0SJNRr9eR5bsaCoVYTB1OJBJIp9NsUbdXx3AJSEWnkTo7O2MSivHiJdByucxmmQCz2Qw7o5jASE5er5cNwRLwf3/5O/d+3v/zayS1AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d6c337c4a5990f5ade81c03cde51d343/507e6/2026-03-30-cover.webp 200w,
/static/d6c337c4a5990f5ade81c03cde51d343/28a80/2026-03-30-cover.webp 400w,
/static/d6c337c4a5990f5ade81c03cde51d343/8d2ea/2026-03-30-cover.webp 800w,
/static/d6c337c4a5990f5ade81c03cde51d343/ff41f/2026-03-30-cover.webp 1125w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d6c337c4a5990f5ade81c03cde51d343/36ca5/2026-03-30-cover.png 200w,
/static/d6c337c4a5990f5ade81c03cde51d343/a3397/2026-03-30-cover.png 400w,
/static/d6c337c4a5990f5ade81c03cde51d343/a331c/2026-03-30-cover.png 800w,
/static/d6c337c4a5990f5ade81c03cde51d343/2165d/2026-03-30-cover.png 1125w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d6c337c4a5990f5ade81c03cde51d343/a331c/2026-03-30-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;One of the first indications of getting old(er) is when people stop getting your movie or music references. Of course, based on this rule, some people are always old. That happens.&lt;/p&gt;
&lt;p&gt;Recently, I realised during &lt;a href=&quot;/en/training/&quot;&gt;my workshops&lt;/a&gt; that referencing Friends is not so cool anymore. It started to happen when I was explaining the Example Mapping technique.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It always starts with “The one where”.&lt;/p&gt;
&lt;p&gt;Just like in Friends.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I started to notice a bit slower head nodding and a bit more awkward smiles from the attendees. I repeated&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You know, like in titles of Friends episodes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And head-nodding stopped; only totally awkwardly polite smiles remained. Definitely, it wasn’t The One Where Everybody Finds Out. So I finally asked:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You weren’t watching Friends, didn’t you?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Obviously, the answer was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Emmm. No…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Okay, then, if you don’t know Friends or the Example Mapping technique, this will be The One Where You Find Out.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s say that we’re working on the guest checkout feature for a hotel management system.&lt;/p&gt;
&lt;p&gt;We could start by asking the business how it works. We could get an answer that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The guest approaches the desk and requests checkout. The clerk inquires about the quality of the products and services, and after receiving an answer, requests the room key. After gathering, the key clerk checks whether the balance is settled. If it’s settled, then proceed with the checkout. Marking the stay as completed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sounds straightforward, but we should already have several questions popping up, e.g. what does it mean that “balance is settled”? We could get quick feedback that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This means that the difference between the sums of all charges and payments is equal to zero.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then we could try to come up with an example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ah, so for instance, when guests haven’t paid upfront for their stay, right?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Right.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Oh, then we need to charge them, right?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Right.&lt;/p&gt;
&lt;p&gt;We could visualise what we discovered in the following way:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/efc380cd4c80121271a324e14ebb57ce/f64ef/em01.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 108.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAWABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAEDBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHqaWRoigAP/8QAGRABAAIDAAAAAAAAAAAAAAAAAQIQEiAx/9oACAEBAAEFAsUYjTw1/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAGhAAAQUBAAAAAAAAAAAAAAAAIQABEBESIP/aAAgBAQAGPwK9IvI5/8QAGhABAAIDAQAAAAAAAAAAAAAAAREhABBRMf/aAAgBAQABPyFYqyTMprswdnE0QQ9vHVc1/9oADAMBAAIAAwAAABCcDzz/xAAVEQEBAAAAAAAAAAAAAAAAAAABIP/aAAgBAwEBPxAFj//EABYRAQEBAAAAAAAAAAAAAAAAABEBIP/aAAgBAgEBPxBiY//EABoQAQEBAQEBAQAAAAAAAAAAAAERACExUYH/2gAIAQEAAT8QCFFZxZcEJl5JrPc5+ARlvQwnod61EiUwjyL8z13/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/efc380cd4c80121271a324e14ebb57ce/507e6/em01.webp 200w,
/static/efc380cd4c80121271a324e14ebb57ce/28a80/em01.webp 400w,
/static/efc380cd4c80121271a324e14ebb57ce/8d2ea/em01.webp 800w,
/static/efc380cd4c80121271a324e14ebb57ce/68fc1/em01.webp 1200w,
/static/efc380cd4c80121271a324e14ebb57ce/43d96/em01.webp 1600w,
/static/efc380cd4c80121271a324e14ebb57ce/fcf3a/em01.webp 3833w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/efc380cd4c80121271a324e14ebb57ce/37402/em01.jpg 200w,
/static/efc380cd4c80121271a324e14ebb57ce/4cda9/em01.jpg 400w,
/static/efc380cd4c80121271a324e14ebb57ce/c60e9/em01.jpg 800w,
/static/efc380cd4c80121271a324e14ebb57ce/6c738/em01.jpg 1200w,
/static/efc380cd4c80121271a324e14ebb57ce/56dca/em01.jpg 1600w,
/static/efc380cd4c80121271a324e14ebb57ce/f64ef/em01.jpg 3833w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/efc380cd4c80121271a324e14ebb57ce/c60e9/em01.jpg&quot;
            alt=&quot;em01&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Now, this generated another flow for us. We have a new feature we weren’t aware of: the guest’s stay payment registration. Let’s try to start this time from the visualisation.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3c74666474a402d1185819dd0dd30931/76df4/em02.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 90.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAASABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIBBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHp3VQaoDQf/8QAGxAAAQQDAAAAAAAAAAAAAAAAAQACETESICH/2gAIAQEAAQUCLTlD0KN9nT//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAaEAEAAQUAAAAAAAAAAAAAAAAAEQEgITEy/9oACAEBAAY/Apl0yo3b/8QAGxAAAwACAwAAAAAAAAAAAAAAAAERMWEgIVH/2gAIAQEAAT8hxgu6jWLUNWXJOXRIvDzgj//aAAwDAQACAAMAAAAQD8AA/8QAFhEBAQEAAAAAAAAAAAAAAAAAASBR/9oACAEDAQE/EA2P/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAHRABAAICAgMAAAAAAAAAAAAAAQAxEVEQIaGx4f/aAAgBAQABPxACDHsL14lPp+Qn2tjVtNprCYcLdxtjfFJ//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/3c74666474a402d1185819dd0dd30931/507e6/em02.webp 200w,
/static/3c74666474a402d1185819dd0dd30931/28a80/em02.webp 400w,
/static/3c74666474a402d1185819dd0dd30931/8d2ea/em02.webp 800w,
/static/3c74666474a402d1185819dd0dd30931/68fc1/em02.webp 1200w,
/static/3c74666474a402d1185819dd0dd30931/43d96/em02.webp 1600w,
/static/3c74666474a402d1185819dd0dd30931/cb374/em02.webp 4196w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/3c74666474a402d1185819dd0dd30931/37402/em02.jpg 200w,
/static/3c74666474a402d1185819dd0dd30931/4cda9/em02.jpg 400w,
/static/3c74666474a402d1185819dd0dd30931/c60e9/em02.jpg 800w,
/static/3c74666474a402d1185819dd0dd30931/6c738/em02.jpg 1200w,
/static/3c74666474a402d1185819dd0dd30931/56dca/em02.jpg 1600w,
/static/3c74666474a402d1185819dd0dd30931/76df4/em02.jpg 4196w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/3c74666474a402d1185819dd0dd30931/c60e9/em02.jpg&quot;
            alt=&quot;em02&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;It’s the one Oskar pays for his stay, because he wants to check out but didn’t pay upfront. The payment is registered, and we can try checking out again. Sounds fine, but we should ask whether there are any rules for payments. It may appear that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Yes, there are some, for instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only guests with a valid credit card can pay with it for their stay,&lt;/li&gt;
&lt;li&gt;Guests paying in cash need to hand it over before accepting the payment.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/80e3ce7255256202221093e26ed0c650/d7e18/em03.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 128%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAaABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAECAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB9PXRFVWWpBVf/8QAGhAAAwADAQAAAAAAAAAAAAAAAAEREiAhI//aAAgBAQABBQJp5egrGdoyO6f/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPwEh/8QAGhAAAQUBAAAAAAAAAAAAAAAAAAEQESAxIf/aAAgBAQAGPwKZNQ62shtf/8QAGxAAAwEAAwEAAAAAAAAAAAAAABEhASAxQZH/2gAIAQEAAT8hfY9SiAqr0eJfDohemjOIajuQbw//2gAMAwEAAgADAAAAENgNfP/EABYRAAMAAAAAAAAAAAAAAAAAAAEQEf/aAAgBAwEBPxACqr//xAAXEQEBAQEAAAAAAAAAAAAAAAABEBEh/9oACAECAQE/EN7ACs//xAAdEAEAAwEAAgMAAAAAAAAAAAABABExIRBBgaHR/9oACAEBAAE/EK4QbBZ9TD5M/Iwhe1CBNw3bQW5x8Bs3LCGpbRycQCNj22WKttqOvgn/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/80e3ce7255256202221093e26ed0c650/507e6/em03.webp 200w,
/static/80e3ce7255256202221093e26ed0c650/28a80/em03.webp 400w,
/static/80e3ce7255256202221093e26ed0c650/8d2ea/em03.webp 800w,
/static/80e3ce7255256202221093e26ed0c650/68fc1/em03.webp 1200w,
/static/80e3ce7255256202221093e26ed0c650/43d96/em03.webp 1600w,
/static/80e3ce7255256202221093e26ed0c650/9feae/em03.webp 3535w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/80e3ce7255256202221093e26ed0c650/37402/em03.jpg 200w,
/static/80e3ce7255256202221093e26ed0c650/4cda9/em03.jpg 400w,
/static/80e3ce7255256202221093e26ed0c650/c60e9/em03.jpg 800w,
/static/80e3ce7255256202221093e26ed0c650/6c738/em03.jpg 1200w,
/static/80e3ce7255256202221093e26ed0c650/56dca/em03.jpg 1600w,
/static/80e3ce7255256202221093e26ed0c650/d7e18/em03.jpg 3535w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/80e3ce7255256202221093e26ed0c650/c60e9/em03.jpg&quot;
            alt=&quot;em03&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;And hey, we just found out new business rules, let’s put them on the board and update our flow to be more precise and reflect our scenario by adding a note that this scenario represents a guest paying with a credit card.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0e7eb455575de888b86d6f5ecd290ab6/954da/em05.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 133.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAECAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAP/aAAwDAQACEAMQAAAB6GmliJNWugQT/8QAHRAAAQMFAQAAAAAAAAAAAAAAAQASMQIREyAhIv/aAAgBAQABBQIipwyIQU7qM39af//EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAEDAQE/ASn/xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPwEh/8QAGxAAAQQDAAAAAAAAAAAAAAAAABARITEBIEH/2gAIAQEABj8CeSyUZMF81//EAB0QAAICAgMBAAAAAAAAAAAAAAERACExgRAgQXH/2gAIAQEAAT8hZAMqjBYD7HAMzMxQO4no1wVF6jONEGOn/9oADAMBAAIAAwAAABDrwgz/xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAwEBPxAFgJ//xAAWEQEBAQAAAAAAAAAAAAAAAAABEBH/2gAIAQIBAT8QhhWf/8QAHBAAAwEBAAMBAAAAAAAAAAAAAREhADEQUWHR/9oACAEBAAE/EAEEDNfcDRV8AfmSvflLCYiI0pqUIG5MC77ykCboPIhANHPr3Cud8IPATf/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/0e7eb455575de888b86d6f5ecd290ab6/507e6/em05.webp 200w,
/static/0e7eb455575de888b86d6f5ecd290ab6/28a80/em05.webp 400w,
/static/0e7eb455575de888b86d6f5ecd290ab6/8d2ea/em05.webp 800w,
/static/0e7eb455575de888b86d6f5ecd290ab6/68fc1/em05.webp 1200w,
/static/0e7eb455575de888b86d6f5ecd290ab6/43d96/em05.webp 1600w,
/static/0e7eb455575de888b86d6f5ecd290ab6/7f500/em05.webp 3459w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/0e7eb455575de888b86d6f5ecd290ab6/37402/em05.jpg 200w,
/static/0e7eb455575de888b86d6f5ecd290ab6/4cda9/em05.jpg 400w,
/static/0e7eb455575de888b86d6f5ecd290ab6/c60e9/em05.jpg 800w,
/static/0e7eb455575de888b86d6f5ecd290ab6/6c738/em05.jpg 1200w,
/static/0e7eb455575de888b86d6f5ecd290ab6/56dca/em05.jpg 1600w,
/static/0e7eb455575de888b86d6f5ecd290ab6/954da/em05.jpg 3459w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/0e7eb455575de888b86d6f5ecd290ab6/c60e9/em05.jpg&quot;
            alt=&quot;em05&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Now, what if the payment fails? Can it fail? Let’s ask the business!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Payment may fail if the payment gateway is unavailable or the issuer rejects it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What if our internet connection is down for the whole day?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The clerk should ask the guest for cash.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What if the guest doesn’t have cash? 🤔&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Then the shift manager can authorise unsettled balance checkout and register a charge with a delayed due date.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here’s the updated flow. The one where Oskar pays for his entire stay with a credit card, but the Internet is down, and he doesn’t have cash.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0c8d32c4f1e6076c202b972f15189be7/55fc4/em04.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 132.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAaABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAIBAwX/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAQL/2gAMAwEAAhADEAAAAfR6XcBUXjLRp//EABwQAAEDBQAAAAAAAAAAAAAAAAEAAhEQEiAxQf/aAAgBAQABBQIh10PQR2BFO4//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAgEBPwGBP//EABcQAAMBAAAAAAAAAAAAAAAAAAARMDL/2gAIAQEABj8CZqf/xAAcEAEAAgIDAQAAAAAAAAAAAAABABEQITFBUXH/2gAIAQEAAT8htAPOtw8M0Nq/YGvEC1YUqdwx3gn/2gAMAwEAAgADAAAAEGDP/P/EABURAQEAAAAAAAAAAAAAAAAAABAB/9oACAEDAQE/ECn/xAAXEQADAQAAAAAAAAAAAAAAAAABEBEh/9oACAECAQE/ELqgSV//xAAdEAACAgIDAQAAAAAAAAAAAAABEQAhMVFBgeHR/9oACAEBAAE/EL2zkk+wsCzdeQCorvxCDCi1eYgArhzBnUjTUsSa6hzCMudxncwn/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/0c8d32c4f1e6076c202b972f15189be7/507e6/em04.webp 200w,
/static/0c8d32c4f1e6076c202b972f15189be7/28a80/em04.webp 400w,
/static/0c8d32c4f1e6076c202b972f15189be7/8d2ea/em04.webp 800w,
/static/0c8d32c4f1e6076c202b972f15189be7/68fc1/em04.webp 1200w,
/static/0c8d32c4f1e6076c202b972f15189be7/43d96/em04.webp 1600w,
/static/0c8d32c4f1e6076c202b972f15189be7/635b9/em04.webp 3477w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/0c8d32c4f1e6076c202b972f15189be7/37402/em04.jpg 200w,
/static/0c8d32c4f1e6076c202b972f15189be7/4cda9/em04.jpg 400w,
/static/0c8d32c4f1e6076c202b972f15189be7/c60e9/em04.jpg 800w,
/static/0c8d32c4f1e6076c202b972f15189be7/6c738/em04.jpg 1200w,
/static/0c8d32c4f1e6076c202b972f15189be7/56dca/em04.jpg 1600w,
/static/0c8d32c4f1e6076c202b972f15189be7/55fc4/em04.jpg 3477w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/0c8d32c4f1e6076c202b972f15189be7/c60e9/em04.jpg&quot;
            alt=&quot;em04&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Now, we found out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A new outcome&lt;/strong&gt;, failed payment,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A new rule&lt;/strong&gt;, that we need Internet access to authorise credit card payment,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A new feature&lt;/strong&gt;, the shift manager can authorise unsettled balance checkout and register a charge with a delayed due date.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How would the authorisation look? How should we register a delayed charge?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We should follow double-entry bookkeeping and register the authorisation (type of payment) with the unbalanced amount to settle the balance for today, and register an additional delayed charge for the same amount&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The flow will look like:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3fa07c1fc1037b8863ec05b948d06f9c/c45c4/em06.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 133%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIBBf/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAAB6lVpWw05bKa0f//EABsQAAMAAgMAAAAAAAAAAAAAAAEQEQISADFB/9oACAEBAAEFAjjlsN0exF7QVOQL/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQIBAT8BIf/EABkQAAIDAQAAAAAAAAAAAAAAABEgACExQf/aAAgBAQAGPwIma1dX/8QAGxAAAgMBAQEAAAAAAAAAAAAAAREAECExQWH/2gAIAQEAAT8hxnvNgNor4KZeRLAPO0TkOAjA9Uja2I4K/9oADAMBAAIAAwAAABAQyrD/xAAXEQADAQAAAAAAAAAAAAAAAAABEBEh/9oACAEDAQE/ENVEX//EABcRAQEBAQAAAAAAAAAAAAAAAAEQESH/2gAIAQIBAT8Q3sEOz//EABwQAQEBAQADAQEAAAAAAAAAAAERACExQVGxYf/aAAgBAQABPxBCEFIX+TLB95BfzUwUiZ7tyUqri+NR6dMPgGWX1pSgV2LpOHrV+BL/ADDBATnNDf/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/3fa07c1fc1037b8863ec05b948d06f9c/507e6/em06.webp 200w,
/static/3fa07c1fc1037b8863ec05b948d06f9c/28a80/em06.webp 400w,
/static/3fa07c1fc1037b8863ec05b948d06f9c/8d2ea/em06.webp 800w,
/static/3fa07c1fc1037b8863ec05b948d06f9c/68fc1/em06.webp 1200w,
/static/3fa07c1fc1037b8863ec05b948d06f9c/43d96/em06.webp 1600w,
/static/3fa07c1fc1037b8863ec05b948d06f9c/16647/em06.webp 3466w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/3fa07c1fc1037b8863ec05b948d06f9c/37402/em06.jpg 200w,
/static/3fa07c1fc1037b8863ec05b948d06f9c/4cda9/em06.jpg 400w,
/static/3fa07c1fc1037b8863ec05b948d06f9c/c60e9/em06.jpg 800w,
/static/3fa07c1fc1037b8863ec05b948d06f9c/6c738/em06.jpg 1200w,
/static/3fa07c1fc1037b8863ec05b948d06f9c/56dca/em06.jpg 1600w,
/static/3fa07c1fc1037b8863ec05b948d06f9c/c45c4/em06.jpg 3466w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/3fa07c1fc1037b8863ec05b948d06f9c/c60e9/em06.jpg&quot;
            alt=&quot;em06&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;And that’s precisely how the Example Mapping session looks like. It’s a structured conversation format created by &lt;a href=&quot;https://mattwynne.net/about&quot;&gt;Matt Wynne&lt;/a&gt;. You take a user story, gather a small group (usually a developer, tester, and someone from the business side), and spend around 25-30 minutes breaking it down together.&lt;/p&gt;
&lt;p&gt;You don’t need a big setup, a huge ceremon, you don’t need sticky notes, you can just use plain text like:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Given:&lt;/strong&gt; Example&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; We use specific features&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then:&lt;/strong&gt; Based on business rules, we get a specific outcome.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d38b3c2f3f4e871ef274e3e3881f6f7f/0c9f7/em07.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAEF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAL/2gAMAwEAAhADEAAAAdirKin/xAAXEAEBAQEAAAAAAAAAAAAAAAARABAh/9oACAEBAAEFAmeRGf/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABUQAQEAAAAAAAAAAAAAAAAAABBB/9oACAEBAAY/Amv/xAAcEAEAAgEFAAAAAAAAAAAAAAABADEQESFRcaH/2gAIAQEAAT8hVrXk2pSsWuAHL3j/2gAMAwEAAgADAAAAEKzP/8QAFxEBAAMAAAAAAAAAAAAAAAAAARARMf/aAAgBAwEBPxADbj//xAAXEQADAQAAAAAAAAAAAAAAAAAQESEx/9oACAECAQE/EK8H/8QAGhABAAMBAQEAAAAAAAAAAAAAAQARITFB0f/aAAgBAQABPxAKBUIGPst1xxonYlTVbeMQVQfNEd5P/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d38b3c2f3f4e871ef274e3e3881f6f7f/507e6/em07.webp 200w,
/static/d38b3c2f3f4e871ef274e3e3881f6f7f/28a80/em07.webp 400w,
/static/d38b3c2f3f4e871ef274e3e3881f6f7f/8d2ea/em07.webp 800w,
/static/d38b3c2f3f4e871ef274e3e3881f6f7f/68fc1/em07.webp 1200w,
/static/d38b3c2f3f4e871ef274e3e3881f6f7f/43d96/em07.webp 1600w,
/static/d38b3c2f3f4e871ef274e3e3881f6f7f/0e4cf/em07.webp 4712w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d38b3c2f3f4e871ef274e3e3881f6f7f/37402/em07.jpg 200w,
/static/d38b3c2f3f4e871ef274e3e3881f6f7f/4cda9/em07.jpg 400w,
/static/d38b3c2f3f4e871ef274e3e3881f6f7f/c60e9/em07.jpg 800w,
/static/d38b3c2f3f4e871ef274e3e3881f6f7f/6c738/em07.jpg 1200w,
/static/d38b3c2f3f4e871ef274e3e3881f6f7f/56dca/em07.jpg 1600w,
/static/d38b3c2f3f4e871ef274e3e3881f6f7f/0c9f7/em07.jpg 4712w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d38b3c2f3f4e871ef274e3e3881f6f7f/c60e9/em07.jpg&quot;
            alt=&quot;em07&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Business people don’t need to give them to you in such form. You can use the interview as I showed above and note it on your own, while you’re discussing stuff. It’s also a nice way to collaborate and visualise your discussions.&lt;/p&gt;
&lt;p&gt;You don’t even need to start with an interview; you can use the Example Mapping as a brainstorming tool to generate as many examples of your (part of the) system. Then, try to model it as you see fit and ask the business for clarifications in the preferred form. It can help facilitate discussion with your team, not only with business stakeholders.&lt;/p&gt;
&lt;p&gt;It’s super helpful, as misunderstandings are expensive. When multiple people try to describe the same rule using real examples, you’ll quickly see where assumptions don’t match. Better to find that out in a 30-minute chat than after two weeks of coding the wrong thing.&lt;/p&gt;
&lt;p&gt;It also works as a readiness check. Too many red cards? The story isn’t ready. Too many blue cards? The story is probably too big. If examples come easily and everyone nods along - you’re good to go.&lt;/p&gt;
&lt;p&gt;There’s also more, as you can try to distil business rules based on the examples and outcomes the business describes.&lt;/p&gt;
&lt;p&gt;It’s worth noting that I cheated you in colours. The original looks like this:&lt;/p&gt;
&lt;p&gt;Why did I change them?&lt;/p&gt;
&lt;p&gt;Example Mapping plays nicely with other collaboration techniques like Event Storming. And if you’re familiar with the Event Storming colour scheme, that’s also the reason why I used it. I aligned them. See:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cucumber.io/blog/bdd/example-mapping-introduction/&quot;&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsSAAALEgHS3X78AAAC80lEQVR42pVTTUwTURDeAoIXFRXRGK8ag8abB72ZGE9eOZh4lcSjMR489YCJUWIialI0UUz5URYQKUIJlG4LbXe73XbL7tLCtkv/lpbaP9ql/5Zxl1LjxQQnmXzvfW9m3uTN9xAAUMneBEAdQWQrZd/ehV0NCuUPfVDU9BaT/XcUHgVoVstxCir7DEa35wiayRFuKW1xSll8NSGR7qtIwwCwljoOy3kjkAi/KgJoobyreVfn0eY6gmofM9CeJNhQYN4Ma9MLEDdTBWCEa4hdhNM8wBm/JHUqgan4wJNEVJMW+DdiLj0I2eTwc4Xf2YFT/B4cF9JwQsY2hYsvk71V2guy/wLWByWK/YzYhRKB8/k4IZTCjlDVs8ztPrDwRRbnM0lGLAC5mTXivtJjwl+O4b4iSwVrMcJXeKoUlJzr98UlAlzoTGVzwQK7XNCM0NFa2MRmYZaIgUsEyRnJP2Ljtfy0VQQLXwFHCHBvuvwiKD/AgiteilQB1neqL5WCecfqx7JzLVd1r1eKFAcpK/UMMW/QBnzTXbAJ7qIjypTJKNdDBL0ePMjkbQIXxAPsOMYF7pkYqc/IZHoxJvNa70rehkTiWNrOJrYxAiKLFpBIuSuPeAnBt8Z/2EQU5rnBCifNQQgsD23iOL60oQWdY6Dm2J6segqLNw4G0tQYSoam2+U3tPtmjRHfnEmImcjVLL52EbFFpj5hfjRn9I35jT5UJLfneojoNLESnKguByZr8rpgT+mv1KXDtWIALYp81Gp1EzNqOBvAuHM5iupYGRk52d3d3YzoqPcdQ1j/Be1kX2dXV1erOfjlpokf3jZ4tVlraAzo1MwGFhg8Wu8QUSGHNRSt68wrGW7ZozrQs0M1uXNwJufof+UoXSp+8DlUSONWNcgHmHpf2NaticuW0LhIxr6VZAfXz2nRmfh+/m9RH8oawXuw10ZsTXkM6yMwz2r33Gl9LVCx/BnKfxfkOLTVGp7EMP9YeokfDWDCVz+Z0F0/bMHf9n1AUKBzC30AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/094109b41a8dda713a1c5e49e1aa3dbe/507e6/em08.webp 200w,
/static/094109b41a8dda713a1c5e49e1aa3dbe/28a80/em08.webp 400w,
/static/094109b41a8dda713a1c5e49e1aa3dbe/8d2ea/em08.webp 800w,
/static/094109b41a8dda713a1c5e49e1aa3dbe/a6570/em08.webp 1007w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/094109b41a8dda713a1c5e49e1aa3dbe/36ca5/em08.png 200w,
/static/094109b41a8dda713a1c5e49e1aa3dbe/a3397/em08.png 400w,
/static/094109b41a8dda713a1c5e49e1aa3dbe/a331c/em08.png 800w,
/static/094109b41a8dda713a1c5e49e1aa3dbe/73ca2/em08.png 1007w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/094109b41a8dda713a1c5e49e1aa3dbe/a331c/em08.png&quot;
            alt=&quot;source: https://cucumber.io/blog/bdd/example-mapping-introduction/&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
    &lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I’m typically using it during modelling sessions to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;brainstorm (read more in &lt;a href=&quot;/en/start-alone-then-together-why-software/&quot;&gt;Start Alone, Then Together: Why Software Modelling Needs Solitary Brainstorming&lt;/a&gt;),&lt;/li&gt;
&lt;li&gt;challenging existing models with real-world examples,&lt;/li&gt;
&lt;li&gt;expanding the model with uncovered (through examples) use cases,&lt;/li&gt;
&lt;li&gt;finding business rules,&lt;/li&gt;
&lt;li&gt;helping with facilitation by looking at the model from a different perspective.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And hot spots and notes, as known from EventStorming, are super helpful here. Read also more in &lt;a href=&quot;/en/the-underestimated-power-of-hot-spots/&quot;&gt;The Underestimated Power of Hot Spots and Notes in EventStorming&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What’s more, if you look at the Given/When/Then pattern, you may notice that it works nicely with Behaviour-Driven Design. I already wrote that &lt;a href=&quot;/en/behaviour_driven_design_is_not_about_tests/&quot;&gt;Behaviour-Driven Design is more than tests&lt;/a&gt;. How to do it? Check &lt;a href=&quot;/en/testing_event_sourcing_emmett_edition/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’ll also expand on it in the next articles. I’m doing the extreme Example Mapping with events, so stay tuned, the more will come.&lt;/p&gt;
&lt;p&gt;For now, check also those materials:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=EtoTML8cuko&quot;&gt;Seb Rose - short, practical and actionable intro to Example Mapping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=WvkBKvMnyuc&quot;&gt;Kenny Baas-Schwegler - showing how to use Example Mapping with EventStorming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cucumber.io/blog/bdd/example-mapping-introduction/&quot;&gt;An introduction by Matt Wynne himself&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://draft.io/example/example-mapping&quot;&gt;Other quick intro by Gojko Adzic&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And most importantly, try it. Take one feature from your system, try to crunch it, or start your design session with this technique.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/en/training/&quot;&gt;Most of the teams I’m working with&lt;/a&gt; are enjoying this technique, as it’s a fun way to get quick, actionable outcomes.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Interactive Rubber Ducking with GenAI]]></title><description><![CDATA[You may already know that I’m a GenAI sceptic. And a general sceptic. Do you know that scepticism comes from the Greek σκέπτομαι (skeptomai…]]></description><link>https://event-driven.io/en/interactive_rubber_ducking_with_gen_ai/</link><guid isPermaLink="false">https://event-driven.io/en/interactive_rubber_ducking_with_gen_ai/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a08c97f5281d31661ba3946fbc3b1ec0/a331c/2026-03-16-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADsklEQVR42g2S6zOcBxTG30koEvdFFFlkiN24rBUSBB2CRazLrssiYpeQHZehLrMkZROXEZGSIhIVKghtc2skTSKZtNJJpy1KkUl0hKZjpv/Gr++HM2fmfHjO85zfEQznDDScb2Ju/jm/LS+y83GbZ4/v8NOL+2zvbDJ97zqP58a4Pz2CLjsFpcKfwb4OFn95yssfZ3k+N8X83CRD19qpqS1H6B8dZWruKT03R+j8aoB4jYbQ2Gha2pr5+fU85s5a9MUZtFaeRhevxNN1H0GBPpwpySMhMYba6hJ++PYmuQUaGk11CJPff8e7f3dZ295h+f0mR5OTsHRxIio+lpcL83R3mCjQqtl69YDZhnzqUkLQx8gJCw8kONSPXE0iY0OdGI3FJKmTEMbvTLG69YFf/1pjYWmZ6PQ0pMEBWDnYkl2gRW8sQREVweWq0zwyFTFcFM0VXRRp4YeRB8rQapOZGv2SDnM9QwNdCNN3Z9nc/Y+VrV3+2PpImDoLG29frCROqLNUFJdmEhwZwamEGB42G2hMDcL4mT8nvFzRyGWUifMbvRcxm01Mjg8h+AcFUVJZwcSDa1yor6appJSkvHz0pXrqG+vJz0vFcEZDuV5LpRhPd1yBwl1CyqFDXFKlYQhVkhEWQpk+H3VOBsJeG2uk3ge4daMMVbg3b0Yv0911HnNLDTPD7Zhrcult0DI+0Er72RLMSemYUtSYVenUxSWQLZdz3N0VNycHBAsLBHsXR4RPrKiuOouppYE7Q228+OYijeVauupy+KJGR3ddFm+G68hRHkEfGcWYvoKOdA1q2REyZAFEeh7ASwS5x85W1JK4I9g6UlqWxuvRIgaqC9GmxyFTyAgNlFKdHEFBuB9tmcdIC/MiMcQf08k0qk/EkS6TUaRUEi31wsNZdLjPGuFYkJQAfz/G2+P4Z9CKAZ2cnMICyitKmei/wMLYVb42ahnQpzBWoSJR4cu52BgyA2QES1woDY8kXozt4SYR3Vki9OkVLN5KYPeJis3rlrzqjeD92grvNlZZF/vK8hK/zz9huqeN7OgAjsql9OhVzDQVM9FooE8ElnLYFzeH/SKQPQi3m0+xPh7G2xEJz0x7GSzcz/zdMf7+sMPGxrpYb1n6c5XlxUURViueHg4opE6UnVTSbUjlqiGRz8VnD5S6YWMrij7sOcPcJXFjlS/9ORLaM7x41Gfk3kgXM30NzPQYud2mQ50g3ioiEJ9P7TjoZo2rvQUudhYEeNoT6uPCQfGVHJ2d+R8P5zMVAGXYwQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/a08c97f5281d31661ba3946fbc3b1ec0/507e6/2026-03-16-cover.webp 200w,
/static/a08c97f5281d31661ba3946fbc3b1ec0/28a80/2026-03-16-cover.webp 400w,
/static/a08c97f5281d31661ba3946fbc3b1ec0/8d2ea/2026-03-16-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/a08c97f5281d31661ba3946fbc3b1ec0/36ca5/2026-03-16-cover.png 200w,
/static/a08c97f5281d31661ba3946fbc3b1ec0/a3397/2026-03-16-cover.png 400w,
/static/a08c97f5281d31661ba3946fbc3b1ec0/a331c/2026-03-16-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/a08c97f5281d31661ba3946fbc3b1ec0/a331c/2026-03-16-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;You may already know that &lt;a href=&quot;/en/the_end_of_coding_wrong_question/&quot;&gt;I’m a GenAI sceptic&lt;/a&gt;. And a general sceptic.&lt;/p&gt;
&lt;p&gt;Do you know that scepticism comes from the Greek &lt;em&gt;σκέπτομαι&lt;/em&gt; (&lt;em&gt;skeptomai&lt;/em&gt;), meaning ‘to search, to think about, or look for’? So my intention is not to say no to everything new, but more to think about it first, and understand before I say yes.&lt;/p&gt;
&lt;p&gt;There’s a lot of stuff about GenAI that makes me smile, but I still understand that my way is my way, and I won’t stop the world. I won’t even try. Thus, I want to research and consider how those tools can help me. I already wrote that I don’t feel like &lt;a href=&quot;/en/requiem-for-a-10x-engineer-dream/&quot;&gt;10x Dev&lt;/a&gt;, but I’m finding more ways to get help from it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One of the ways that helped me is something I call &lt;em&gt;“Interactive Rubber-Ducking”&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Initially, I called it just &lt;a href=&quot;/en/start-alone-then-together-why-software/&quot;&gt;brainstorming&lt;/a&gt;, but that wouldn’t be precise, as I’m not using it to brainstorm ideas, more to challenge and clarify them.&lt;/p&gt;
&lt;p&gt;Most of the code I write nowadays is done in &lt;a href=&quot;https://github.com/oskardudycz/&quot;&gt;my OSS projects&lt;/a&gt;. I’m grateful to have a &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;great community&lt;/a&gt; with people actively contributing in different ways; still, the canonical design and code work is on my side. As I work in an event-driven niche, I’m often alone with my own thoughts. I try to use the &lt;a href=&quot;/en/workflow-engine-design-proposal-tell/&quot;&gt;RFC process&lt;/a&gt; and discuss it with other fellow humans, but they are not always available. Even if they do, to avoid wasting their time, I need to know what to tell or ask them. I need to give some proposals (with alternatives) to have an &lt;a href=&quot;/en/fifteen_tips_on_how_to_run_meetings_effectively/&quot;&gt;effective discussion&lt;/a&gt;. I may seem organised, but that’s not always the thing. Sitting in your own head is not a great place to be in general. If you’re a technical leader or an architect, I’m sure that you know that solitude too well.&lt;/p&gt;
&lt;p&gt;GenAI tools are not great sparing partners. They’re &lt;em&gt;Yes men&lt;/em&gt;. If they read this article, they’d for sure confirm it. They’d probably do it even without reading it. Of course, you can ask them not to be &lt;a href=&quot;https://www.merriam-webster.com/dictionary/sycophant&quot;&gt;sycophant&lt;/a&gt;. You can ask numerous MUSTS with capital letters and bolded &lt;strong&gt;NEVER&lt;/strong&gt; here and there, and it can help, but it won’t fully beat the way they were trained.&lt;/p&gt;
&lt;p&gt;And talking to yourself is a similar experience: you’ll find numerous ways to justify your own decisions, and looking at the same place for too long will make you miss obvious blind spots.&lt;/p&gt;
&lt;p&gt;Ok, so why would we take those two blind “people” and try to make them help each other?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s kinda what “Interactive Rubber-Ducking” is.&lt;/strong&gt; It takes a blind human with an idea, and another blind not-so-human asking questions. It starts with such a prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ask me one question at a time so we can develop a thorough, step-by-step spec for this idea. Each question should build on my previous answers, and our end goal is to have a detailed specification I can hand off to a developer. Let’s do this iteratively and dig into every relevant detail. Remember, only one question at a time.&lt;/p&gt;
&lt;p&gt;Once we are done, save the spec as spec.md&lt;/p&gt;
&lt;p&gt;Before asking another question, store the previous one with the answer in qa.md. Write literally the question and answer, not just a summary.&lt;/p&gt;
&lt;p&gt;Here’s the idea:&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Don’t treat it as &lt;em&gt;“one magical prompt that will change your life”&lt;/em&gt;. Most important is why we’re doing it, what happens next, and who’s actually doing the work. Spoiler alert: it’s not LLM.&lt;/p&gt;
&lt;p&gt;I’m using it as a command in Claude Code, and, most importantly, with beefier models like Opus, which can better reason and ask better questions. Doing it with lower-level models always gave me much worse results.&lt;/p&gt;
&lt;p&gt;I’m using it with Claude Code, not Claude Chat, because I want the model to scan my codebase. I can ask it to look in certain areas or to reference my answers. I can even ask to search the web or MCPs like &lt;a href=&quot;https://context7.com/&quot;&gt;Context 7&lt;/a&gt; to check documentation and APIs for popular libraries. Then it’s getting more into brainstorming sometimes, than rubber-ducking, but that’s fine.&lt;/p&gt;
&lt;p&gt;As a result, we’ll get two artefacts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;qa.md&lt;/strong&gt; - with the log of the back-and-forth discussion,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;spec.md&lt;/strong&gt; - in theory spec built by LLM, but imho it’s more of a concise summary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It may look like Specification-Driven Design, but it’s not.&lt;/p&gt;
&lt;p&gt;My goal for this exercise is not to get an actionable specification.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal is to get our LLM-based Rubber Duck to ask us hard questions and make us think, not to make the LLM think for us. Find blind spots, and challenge our thinking.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But we’re drivers, we need to know what we want to do, we need to know all the WHYs, and we also need to know HOW. LLM is here to help, but not to do creative work for us. It just pulls it out from our heads.&lt;/p&gt;
&lt;p&gt;It also helps to see how our design may be seen by others, especially such mediocre thinkers as LLMs.&lt;/p&gt;
&lt;p&gt;I don’t expect the Agent to be able to start implementing the spec. I expect it to reflect all considerations and summarise findings. I’m always double-checking to make sure it includes all the important points. If not, I’ll keep doing Q&amp;#x26;A until I’m satisfied.&lt;/p&gt;
&lt;p&gt;Having both of those files will allow us to keep a full discussion without losing important details, and a shorter version. We can feed that to another model for review, or try to work on tasks and develop a more detailed plan. Sometimes I plan on my own; for simpler tasks, I may ask the LLM to do it fully. Usually, I’m driving the LLM step by step, passing just specific asks.&lt;/p&gt;
&lt;p&gt;The example? Why not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I recently did such an exercise, trying to narrow down how to introduce the &lt;em&gt;Second-level cache&lt;/em&gt; to &lt;a href=&quot;https://github.com/event-driven-io/pongo&quot;&gt;Pongo&lt;/a&gt; and &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;.&lt;/strong&gt; What’s &lt;em&gt;Second-level cache&lt;/em&gt;? A Second-level cache is a local store of data managed by the persistence provider to improve application performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why do I want to introduce it?&lt;/strong&gt; Because I got an &lt;a href=&quot;https://github.com/event-driven-io/emmett/issues/322&quot;&gt;issue from the user&lt;/a&gt; that &lt;a href=&quot;/en/rebuilding_event_driven_read_models/&quot;&gt;rebuilding projections&lt;/a&gt; with a lot events can take too long. One of the reasons is that applying an event on a projection takes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;loading the current state,&lt;/li&gt;
&lt;li&gt;updating it,&lt;/li&gt;
&lt;li&gt;storing the result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are two operations per event. If we have a batch of 100 events, that’d mean 200 operations; for 1000 events, this would be 2000, so the classical N+1 problem. We could do it differently, and within a batch:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;group events by the target documents,&lt;/li&gt;
&lt;li&gt;load all of them in one operation by finding all documents within an array of ids (taken from events),&lt;/li&gt;
&lt;li&gt;caching them,&lt;/li&gt;
&lt;li&gt;applying events in memory,&lt;/li&gt;
&lt;li&gt;storing updated documents in one operation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then we’ll get at worst 102 operations for 100 events and 1002 for 1000 events, so a linear increase.&lt;/p&gt;
&lt;p&gt;Still, I decided to add batching and introduce 2nd-level cache as a solution. I and my experience were a driving force behind that. I could,  of course, rubber duck it, but I had that already planned, not just detailed. I also did some initial research before doing the session.&lt;/p&gt;
&lt;p&gt;Let me show you the result below. Not too bad if you ask me. Of course, the task wasn’t very innovative; it’s a standard way for mature solutions. Still questions were surprisingly good and helped me.&lt;/p&gt;
&lt;p&gt;I hope this log will show you (besides how bad I’m at typos) some insight into how I think, talk, and consider the design.&lt;/p&gt;
&lt;p&gt;Tell me your thoughts, if that matches your way, if you haven’t done it yet, try it and tell me how it went!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;interactive-rubber-ducking-example&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#interactive-rubber-ducking-example&quot; aria-label=&quot;interactive rubber ducking example permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Interactive Rubber Ducking example&lt;/h2&gt;
&lt;h3 id=&quot;my-idea&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#my-idea&quot; aria-label=&quot;my idea permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;My idea&lt;/h3&gt;
&lt;p&gt;I’d like to provide 2nd-level cache to Pongo. I want to either use keyv (&lt;a href=&quot;https://www.npmjs.com/package/keyv&quot;&gt;https://www.npmjs.com/package/keyv&lt;/a&gt;) or lru-cache (&lt;a href=&quot;https://www.npmjs.com/package/lru-cache&quot;&gt;https://www.npmjs.com/package/lru-cache&lt;/a&gt;) packages.&lt;/p&gt;
&lt;p&gt;For now, I’d like to do it by id.&lt;/p&gt;
&lt;p&gt;I probably need to either extend pongoCollection src/packages/pongo/src/core/collection/pongoCollection.ts with findeOneById and findManyByIds methods, or check if _id is passed in and then try to access the cache.&lt;/p&gt;
&lt;p&gt;Cache should be set up either on the pongo collection, pongo db or pongoClient.&lt;/p&gt;
&lt;p&gt;Optimistic Concurrency errors should invalidate cache (as that means someone else updated the record in db, e.g. through a different node).&lt;/p&gt;
&lt;p&gt;InsertMany and insertOne should put it into the cache.&lt;/p&gt;
&lt;p&gt;I’d also like to expand the handle method to allow passing either an id or an array of IDs to support batch loading of entities and improve performance, and to support batch update/insert/delete.&lt;/p&gt;
&lt;p&gt;Thoughts?&lt;/p&gt;
&lt;p&gt;Which defaults should I set? (Of course, allowing users to override it).&lt;/p&gt;
&lt;p&gt;Check the related code I pasted. I want to keep it aligned and as non-invasive as possible.&lt;/p&gt;
&lt;h3 id=&quot;q1-cache-library-choice--lru-cache-keyv-or-custom-interface&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q1-cache-library-choice--lru-cache-keyv-or-custom-interface&quot; aria-label=&quot;q1 cache library choice  lru cache keyv or custom interface permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q1: Cache library choice — lru-cache, keyv, or custom interface?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; You mentioned both &lt;code class=&quot;language-text&quot;&gt;keyv&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;lru-cache&lt;/code&gt;. They pull in different directions. Which trade-off do you prefer?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A) Start with &lt;code class=&quot;language-text&quot;&gt;lru-cache&lt;/code&gt;, optionally let users swap adapters later.&lt;/li&gt;
&lt;li&gt;B) Start with &lt;code class=&quot;language-text&quot;&gt;keyv&lt;/code&gt; so cross-node sharing works out of the box.&lt;/li&gt;
&lt;li&gt;C) Define a thin cache interface (&lt;code class=&quot;language-text&quot;&gt;get&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;set&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;delete&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;clear&lt;/code&gt;) and ship &lt;code class=&quot;language-text&quot;&gt;lru-cache&lt;/code&gt; as the default adapter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Custom cache interface (option C). No dependency on external APIs — keep it pluggable. Start with an in-memory provider only. Considered writing a custom LRU with me, but wants to ship fast. Key point: don’t stick to any third-party API surface, allow pluggability.&lt;/p&gt;
&lt;h3 id=&quot;q2-where-should-the-cache-live-in-pongos-hierarchy&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q2-where-should-the-cache-live-in-pongos-hierarchy&quot; aria-label=&quot;q2 where should the cache live in pongos hierarchy permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q2: Where should the cache live in Pongo’s hierarchy?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Three attachment points: A) PongoClient level (shared across all), B) PongoDb level (per database), C) PongoCollection level (per collection). Or configurable at multiple levels?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Allow defining it at different levels — cascading configuration. Set a default higher up, override per collection if needed.&lt;/p&gt;
&lt;h3 id=&quot;q3-inheritanceoverride-behavior-for-cascading-cache-config&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q3-inheritanceoverride-behavior-for-cascading-cache-config&quot; aria-label=&quot;q3 inheritanceoverride behavior for cascading cache config permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q3: Inheritance/override behavior for cascading cache config?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; For the cascading config — what should the inheritance/override behavior be?&lt;/p&gt;
&lt;p&gt;Two sub-questions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Should a lower level be able to &lt;em&gt;disable&lt;/em&gt; caching even if a parent enables it?&lt;/li&gt;
&lt;li&gt;Should cache &lt;em&gt;settings&lt;/em&gt; (like max size, TTL) also cascade, or only the cache provider instance itself?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Yes. Cache should be an object with type, general params and specific for this type, or &lt;code class=&quot;language-text&quot;&gt;&apos;disabled&apos;&lt;/code&gt;. That’s a proper devex.&lt;/li&gt;
&lt;li&gt;Yes, as above. But if one selects a different cache type then general params should be inherited but type-specific should not be inherited and should fallback to defaults.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Claude’s thoughts:&lt;/strong&gt; The &lt;code class=&quot;language-text&quot;&gt;CacheConfig | &apos;disabled&apos;&lt;/code&gt; pattern is clean — discriminated unions play nicely with TypeScript. The inheritance rule makes sense: general params (TTL, max size) cascade, but type-specific params reset to defaults when you switch providers, avoiding leaking nonsensical config (e.g., a Redis connection string bleeding into an in-memory cache). Also worth distinguishing between “I didn’t specify anything” (inherit from parent via &lt;code class=&quot;language-text&quot;&gt;undefined&lt;/code&gt;) and “I explicitly want defaults” (reset) — &lt;code class=&quot;language-text&quot;&gt;undefined&lt;/code&gt; vs &lt;code class=&quot;language-text&quot;&gt;&apos;disabled&apos;&lt;/code&gt; vs &lt;code class=&quot;language-text&quot;&gt;CacheConfig&lt;/code&gt; gives a clean three-state: inherit / off / explicit.&lt;/p&gt;
&lt;h3 id=&quot;q4-cache-interface-shape--async-batch-ops-scoping&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q4-cache-interface-shape--async-batch-ops-scoping&quot; aria-label=&quot;q4 cache interface shape  async batch ops scoping permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q4: Cache interface shape — async, batch ops, scoping?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Settling the cache interface details:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Async or sync? Making the interface &lt;code class=&quot;language-text&quot;&gt;Promise&lt;/code&gt;-based from the start keeps it pluggable. Sound right?&lt;/li&gt;
&lt;li&gt;Batch methods (&lt;code class=&quot;language-text&quot;&gt;getMany&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;setMany&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;deleteMany&lt;/code&gt;) — should the cache interface support batch ops natively, or loop internally?&lt;/li&gt;
&lt;li&gt;Should &lt;code class=&quot;language-text&quot;&gt;clear()&lt;/code&gt; be scoped per collection, or always clear the entire cache?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Maybe we could return either promise or no promise, then if we await it, it would support both? Also ensure that settings are close to what lru-cache and keyv are having.&lt;/li&gt;
&lt;li&gt;Having them (even as syntactic sugar) would be a good devex.&lt;/li&gt;
&lt;li&gt;We could pass down not only settings, but also the cache provider instance, then if settings are the same we can reuse cache without recreating it. If set had ttl provided, then we could do it by overriding those TTLs on item level. ClearAll probably would be something we should expose only for advanced, typically user should not be aware of cache.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Claude’s thoughts:&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;MaybePromise&amp;lt;T&gt;&lt;/code&gt; (i.e. &lt;code class=&quot;language-text&quot;&gt;T | PromiseLike&amp;lt;T&gt;&lt;/code&gt;) works — &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; on a non-Promise just resolves immediately. No overhead for sync providers, no breaking change for async ones. Passing a cache instance is smart: config becomes either settings (Pongo creates the cache) or a pre-built instance (user brings their own). Multiple collections can explicitly share one cache instance. &lt;code class=&quot;language-text&quot;&gt;clear()&lt;/code&gt; should be internal/advanced — scope by collection prefix internally so a collection-level clear doesn’t nuke another collection’s entries when sharing an instance. For aligning with lru-cache/keyv settings: both support &lt;code class=&quot;language-text&quot;&gt;max&lt;/code&gt; (max entries) and &lt;code class=&quot;language-text&quot;&gt;ttl&lt;/code&gt; (TTL in ms). Start with &lt;code class=&quot;language-text&quot;&gt;max&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;ttl&lt;/code&gt; as the general params.&lt;/p&gt;
&lt;h3 id=&quot;q5-cache-key-strategy&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q5-cache-key-strategy&quot; aria-label=&quot;q5 cache key strategy permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q5: Cache key strategy&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Since cache instances might be shared across collections, how to avoid key collisions?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A) Pongo manages key prefixing internally (&lt;code class=&quot;language-text&quot;&gt;{dbName}:{collectionName}:{id}&lt;/code&gt;). The cache interface works with plain string keys, namespacing is Pongo’s concern.&lt;/li&gt;
&lt;li&gt;B) Key is just the document &lt;code class=&quot;language-text&quot;&gt;_id&lt;/code&gt;. Simpler but only safe with per-collection instances.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; A, definitely.&lt;/p&gt;
&lt;h3 id=&quot;q6-cache-invalidation-and-consistency-with-optimistic-concurrency&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q6-cache-invalidation-and-consistency-with-optimistic-concurrency&quot; aria-label=&quot;q6 cache invalidation and consistency with optimistic concurrency permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q6: Cache invalidation and consistency with optimistic concurrency&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; How should cache interact with different operations?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Successful writes → update/evict cache. Clear.&lt;/li&gt;
&lt;li&gt;Concurrency errors → evict stale entry. Clear.&lt;/li&gt;
&lt;li&gt;Reads with &lt;code class=&quot;language-text&quot;&gt;_id&lt;/code&gt; filter → cache hit or miss-then-populate. Clear.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;handle&lt;/code&gt; method — should the read inside &lt;code class=&quot;language-text&quot;&gt;handle&lt;/code&gt; use the cache (risking more concurrency errors on stale data) or always go to DB?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Definitely use cache. We could consider disabling cache through handle options.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude’s thoughts:&lt;/strong&gt; Use cache by default — optimistic concurrency is the safety net. If cached version is stale, write fails, we evict, caller can retry. Adding &lt;code class=&quot;language-text&quot;&gt;skipCache?: boolean&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;HandleOptions&lt;/code&gt; is a clean escape hatch for high-contention cases.&lt;/p&gt;
&lt;h3 id=&quot;q7-should-skipcache-be-available-on-read-methods-too&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q7-should-skipcache-be-available-on-read-methods-too&quot; aria-label=&quot;q7 should skipcache be available on read methods too permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q7: Should &lt;code class=&quot;language-text&quot;&gt;skipCache&lt;/code&gt; be available on read methods too?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Should &lt;code class=&quot;language-text&quot;&gt;skipCache&lt;/code&gt; also be available on &lt;code class=&quot;language-text&quot;&gt;findOne&lt;/code&gt; and other read methods for symmetry, or keep it &lt;code class=&quot;language-text&quot;&gt;handle&lt;/code&gt;-only?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Good point, having &lt;code class=&quot;language-text&quot;&gt;skipCache&lt;/code&gt; on find methods (also &lt;code class=&quot;language-text&quot;&gt;findMany&lt;/code&gt; etc.) would be good.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude’s thoughts:&lt;/strong&gt; Consistent API surface. Add &lt;code class=&quot;language-text&quot;&gt;skipCache?: boolean&lt;/code&gt; on the shared options type (e.g. &lt;code class=&quot;language-text&quot;&gt;CollectionOperationOptions&lt;/code&gt;) so it’s available everywhere without repeating yourself.&lt;/p&gt;
&lt;h3 id=&quot;q8-batch-operations-on-handle--signature-and-handler-shape&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q8-batch-operations-on-handle--signature-and-handler-shape&quot; aria-label=&quot;q8 batch operations on handle  signature and handler shape permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q8: Batch operations on &lt;code class=&quot;language-text&quot;&gt;handle&lt;/code&gt; — signature and handler shape&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; You want &lt;code class=&quot;language-text&quot;&gt;handle&lt;/code&gt; to accept an id or array of ids. Two design choices:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Overloaded signature (&lt;code class=&quot;language-text&quot;&gt;handle(id: string | string[], ...)&lt;/code&gt;) vs separate method (&lt;code class=&quot;language-text&quot;&gt;handleMany&lt;/code&gt;)?&lt;/li&gt;
&lt;li&gt;Handler shape for batch: A) still receives/returns one document at a time (Pongo batches DB ops internally) or B) receives/returns full array?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also noted: since PongoDocuments always have &lt;code class=&quot;language-text&quot;&gt;_version&lt;/code&gt;, if someone provides &lt;code class=&quot;language-text&quot;&gt;expectedVersion&lt;/code&gt; we could compare it against the cached version and decide if it’s stale without hitting the DB.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Option A for handler shape. &lt;code class=&quot;language-text&quot;&gt;handleMany&lt;/code&gt; would be consistent with other APIs, but single id or array (&lt;code class=&quot;language-text&quot;&gt;string | string[]&lt;/code&gt;) is clean enough.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude’s thoughts:&lt;/strong&gt; Version comparison in cache is a cheap short-circuit — if cached &lt;code class=&quot;language-text&quot;&gt;_version&lt;/code&gt; doesn’t match &lt;code class=&quot;language-text&quot;&gt;expectedVersion&lt;/code&gt;, we know it’s stale without a round trip. On signature: TypeScript overloads can narrow the return type (&lt;code class=&quot;language-text&quot;&gt;string&lt;/code&gt; → single result, &lt;code class=&quot;language-text&quot;&gt;string[]&lt;/code&gt; → array). &lt;code class=&quot;language-text&quot;&gt;handle&lt;/code&gt; is a higher-level orchestration method, not direct CRUD, so a union input feels natural. Go with &lt;code class=&quot;language-text&quot;&gt;handle(id: string | string[], handler, options?)&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;q9-defaults--max-entries-ttl-enabled-by-default&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q9-defaults--max-entries-ttl-enabled-by-default&quot; aria-label=&quot;q9 defaults  max entries ttl enabled by default permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q9: Defaults — max entries, TTL, enabled by default?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; What defaults to set? Proposed: &lt;code class=&quot;language-text&quot;&gt;max: 1000&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;ttl: 60_000&lt;/code&gt; (1 min), and should cache be enabled by default or opt-in?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Stick to recommended lru-cache defaults for now. Enable cache by default.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude’s thoughts:&lt;/strong&gt; Enabling by default works because: (1) optimistic concurrency is always the safety net, (2) it’s document-by-id only so invalidation is straightforward, (3) users can set &lt;code class=&quot;language-text&quot;&gt;&apos;disabled&apos;&lt;/code&gt;. &lt;code class=&quot;language-text&quot;&gt;lru-cache&lt;/code&gt; requires &lt;code class=&quot;language-text&quot;&gt;max&lt;/code&gt; to be set (no default) and has no default TTL. Suggested: &lt;code class=&quot;language-text&quot;&gt;max: 1000&lt;/code&gt;, no TTL (rely on LRU eviction + invalidation on writes/concurrency errors) — most lru-cache-aligned.&lt;/p&gt;
&lt;h3 id=&quot;q10-ttl-or-no-ttl-as-staleness-backstop&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q10-ttl-or-no-ttl-as-staleness-backstop&quot; aria-label=&quot;q10 ttl or no ttl as staleness backstop permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q10: TTL or no TTL as staleness backstop?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; No TTL means cached entries live until LRU-evicted or invalidated by a Pongo write. If something updates the DB outside this Pongo instance (another service, direct SQL), the cache won’t know. Options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A) No TTL, accept the trade-off — OC catches it on next write.&lt;/li&gt;
&lt;li&gt;B) Set a conservative default TTL (e.g., 5 minutes) as a staleness bound.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Fine with using TTL and sometimes hitting OC issue. OC assumes rare conflicts.&lt;/p&gt;
&lt;h3 id=&quot;q11-where-does-cache-integration-hook-into-existing-code&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q11-where-does-cache-integration-hook-into-existing-code&quot; aria-label=&quot;q11 where does cache integration hook into existing code permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q11: Where does cache integration hook into existing code?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Two approaches for minimal invasiveness:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Wrap at the &lt;code class=&quot;language-text&quot;&gt;pongoCollection&lt;/code&gt; level — intercept operations inside the factory function, driver/SQL layers untouched.&lt;/li&gt;
&lt;li&gt;Decorator/wrapper pattern — &lt;code class=&quot;language-text&quot;&gt;cachedPongoCollection&lt;/code&gt; wraps &lt;code class=&quot;language-text&quot;&gt;PongoCollection&lt;/code&gt;, adds cache logic around delegation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; Leaning more to option 1. Thinks it’ll be easier to make observability precise eventually.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude’s assessment:&lt;/strong&gt; Agreed with option 1. Cache decisions depend on internal state (whether filter targets &lt;code class=&quot;language-text&quot;&gt;_id&lt;/code&gt;, document &lt;code class=&quot;language-text&quot;&gt;_version&lt;/code&gt;, write success vs concurrency error). A wrapper would need to re-inspect all of that from the outside. For observability, cache hit/miss logic next to the actual DB call enables precise metrics in context. Pongo’s factory functions already compose behavior internally (transactions, schema migration) — cache is another concern at the same level. A decorator would duplicate filter-inspection logic and add indirection that doesn’t carry its weight.&lt;/p&gt;
&lt;h3 id=&quot;q12-cache-interaction-with-transactions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q12-cache-interaction-with-transactions&quot; aria-label=&quot;q12 cache interaction with transactions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q12: Cache interaction with transactions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; How should cache interact with transactions?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Should writes within a transaction update the cache immediately, or only after commit?&lt;/li&gt;
&lt;li&gt;Should reads within a transaction check the cache?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;After commit.&lt;/li&gt;
&lt;li&gt;We should use cache, but allow skipping it on transaction or session options. We can also allow setting cache settings per session.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Claude’s thoughts:&lt;/strong&gt; Reads within a transaction benefiting from cache is more aggressive but logical — most transactions are short-lived. The narrow case: &lt;code class=&quot;language-text&quot;&gt;insertOne&lt;/code&gt; then &lt;code class=&quot;language-text&quot;&gt;findOne&lt;/code&gt; same &lt;code class=&quot;language-text&quot;&gt;_id&lt;/code&gt; within a transaction — cache won’t have the uncommitted insert, falls through to DB, gets the right answer anyway. No correctness issue, just no perf benefit for that case. Per-session cache settings fit the cascading pattern (e.g., &lt;code class=&quot;language-text&quot;&gt;startSession({ cache: &apos;disabled&apos; })&lt;/code&gt; for batch migrations). Proposed cascade priority: client → db → collection → session → per-operation &lt;code class=&quot;language-text&quot;&gt;skipCache&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;q13-cascade-priority--does-session-override-collection&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q13-cascade-priority--does-session-override-collection&quot; aria-label=&quot;q13 cascade priority  does session override collection permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q13: Cascade priority — does session override collection?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Does the cascade priority &lt;strong&gt;client → db → collection → session → per-operation &lt;code class=&quot;language-text&quot;&gt;skipCache&lt;/code&gt;&lt;/strong&gt; feel right? Session overriding collection is the non-obvious part — the argument being that session is the most immediate runtime context, while collection is structural config. Or would you prefer collection always wins over session?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; That’s fine. Session should precede collection, as normally you create session to e.g. share connection, and join the number of operations logically, so it’d make sense actually to also do it to e.g. override settings like caching.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude’s thoughts:&lt;/strong&gt; Session as logical grouping of operations with shared context (connection, transaction) — overriding cache settings at that level is extending the same concept. Bulk import in a session? Set cache disabled once on the session instead of &lt;code class=&quot;language-text&quot;&gt;skipCache&lt;/code&gt; on every call. Final cascade: client → db → collection → session → per-operation.&lt;/p&gt;
&lt;h3 id=&quot;q14-cache-provider-error-handling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q14-cache-provider-error-handling&quot; aria-label=&quot;q14 cache provider error handling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q14: Cache provider error handling&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; If the cache provider throws (e.g., Redis connection drops), should Pongo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A) Treat it as a cache miss — fall through to DB silently, maybe log a warning. Cache failures never break the application.&lt;/li&gt;
&lt;li&gt;B) Propagate the error — let the caller deal with it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt; A — yes, when we add observability to Pongo, we need to make it also a metric and trace event/attribute/log entry.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Claude’s thoughts:&lt;/strong&gt; Cache is an optimization, not a correctness requirement. Silent degradation now, observable degradation when the observability layer lands.&lt;/p&gt;
&lt;h3 id=&quot;q15-cache-warming-eventshooks-and-delete-operations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#q15-cache-warming-eventshooks-and-delete-operations&quot; aria-label=&quot;q15 cache warming eventshooks and delete operations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Q15: Cache warming, events/hooks, and delete operations&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Question:&lt;/strong&gt; Three remaining areas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Cache warming — should Pongo support pre-populating the cache on startup?&lt;/li&gt;
&lt;li&gt;Cache events/hooks — should users be able to listen to cache hits/misses/evictions?&lt;/li&gt;
&lt;li&gt;Delete operations — &lt;code class=&quot;language-text&quot;&gt;deleteOne&lt;/code&gt;/&lt;code class=&quot;language-text&quot;&gt;deleteMany&lt;/code&gt; should evict from cache. Anything non-obvious there?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Answer:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For now keep it as a future concept, or define pluggable strategy for users.&lt;/li&gt;
&lt;li&gt;You can add the basic hook to the Pongo Cache wrapper. Shouldn’t be that hard to just call some function.&lt;/li&gt;
&lt;li&gt;Yes, they should evict if they can, we can delete (for now) when &lt;code class=&quot;language-text&quot;&gt;deleteMany&lt;/code&gt; deletes by ids.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Claude’s thoughts:&lt;/strong&gt; Cache warming deferred — can add a strategy hook later. Basic event callbacks (onHit, onMiss, onEvict, onError) on the cache wrapper are low-cost and useful for debugging even before full observability. Delete eviction scoped to id-based deletes for now — filter-based &lt;code class=&quot;language-text&quot;&gt;deleteMany&lt;/code&gt; doesn’t evict (no way to know which ids were affected without querying first).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;interactive-rubber-ducking-result&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#interactive-rubber-ducking-result&quot; aria-label=&quot;interactive rubber ducking result permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Interactive Rubber Ducking result&lt;/h2&gt;
&lt;h3 id=&quot;pongo-2nd-level-cache--specification&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pongo-2nd-level-cache--specification&quot; aria-label=&quot;pongo 2nd level cache  specification permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pongo 2nd Level Cache — Specification&lt;/h3&gt;
&lt;h3 id=&quot;overview&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#overview&quot; aria-label=&quot;overview permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Overview&lt;/h3&gt;
&lt;p&gt;Add a document-level (by &lt;code class=&quot;language-text&quot;&gt;_id&lt;/code&gt;) 2nd level cache to Pongo with a pluggable provider interface. Ships with an in-memory provider backed by &lt;code class=&quot;language-text&quot;&gt;lru-cache&lt;/code&gt;. Enabled by default. Designed to be minimally invasive to existing code.&lt;/p&gt;
&lt;h3 id=&quot;cache-interface&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cache-interface&quot; aria-label=&quot;cache interface permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cache Interface&lt;/h3&gt;
&lt;h4 id=&quot;pongocacheprovidert&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pongocacheprovidert&quot; aria-label=&quot;pongocacheprovidert permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;PongoCacheProvider&amp;lt;T&gt;&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;A thin, Pongo-owned interface. No coupling to any third-party API.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MaybePromise&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PromiseLike&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PongoCacheProvider&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MaybePromise&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PongoDocument &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PongoDocument&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ttl&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MaybePromise&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MaybePromise&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;getMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keys&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MaybePromise&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;PongoDocument &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;setMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;entries&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PongoDocument&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; ttl&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MaybePromise&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;deleteMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;keys&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MaybePromise&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MaybePromise&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;MaybePromise&lt;/code&gt; return types: sync providers (in-memory) return values directly, async providers (Redis) return Promises. &lt;code class=&quot;language-text&quot;&gt;await&lt;/code&gt; handles both transparently.&lt;/li&gt;
&lt;li&gt;Batch methods (&lt;code class=&quot;language-text&quot;&gt;getMany&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;setMany&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;deleteMany&lt;/code&gt;) are first-class. Default in-memory implementation may loop internally, but the interface allows optimized batch ops for external providers.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;clear()&lt;/code&gt; is internal/advanced — not exposed to typical users. When sharing a cache instance across collections, scoping is handled via key prefixing by Pongo, not by the provider.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cache-key-strategy&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cache-key-strategy&quot; aria-label=&quot;cache key strategy permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cache key strategy&lt;/h4&gt;
&lt;p&gt;Pongo manages key prefixing internally: &lt;code class=&quot;language-text&quot;&gt;{dbName}:{collectionName}:{documentId}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The cache provider works with plain string keys — namespacing is Pongo’s concern, not the provider’s.&lt;/p&gt;
&lt;h4 id=&quot;event-hooks&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#event-hooks&quot; aria-label=&quot;event hooks permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Event hooks&lt;/h4&gt;
&lt;p&gt;The Pongo cache wrapper supports basic callbacks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onHit?(key: string): void&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onMiss?(key: string): void&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onEvict?(key: string): void&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;onError?(error: unknown, operation: string): void&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are optional and intended for debugging and future observability integration.&lt;/p&gt;
&lt;h3 id=&quot;configuration&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#configuration&quot; aria-label=&quot;configuration permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Configuration&lt;/h3&gt;
&lt;h4 id=&quot;cacheconfig&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cacheconfig&quot; aria-label=&quot;cacheconfig permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;CacheConfig&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CacheConfig&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
 type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;               &lt;span class=&quot;token comment&quot;&gt;// e.g., &apos;in-memory&apos;, &apos;redis&apos;, etc.&lt;/span&gt;
 max&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;               &lt;span class=&quot;token comment&quot;&gt;// max entries (general param, cascades)&lt;/span&gt;
 ttl&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;               &lt;span class=&quot;token comment&quot;&gt;// TTL in ms (general param, cascades)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// type-specific options live here too, keyed by type&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;disabled&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Three states:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;undefined&lt;/code&gt; — inherit from parent level&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;&apos;disabled&apos;&lt;/code&gt; — explicitly turn off caching at this level&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;CacheConfig&lt;/code&gt; object — explicit configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cascading-configuration&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cascading-configuration&quot; aria-label=&quot;cascading configuration permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cascading configuration&lt;/h4&gt;
&lt;p&gt;Cache config can be set at multiple levels. Each level inherits from its parent unless explicitly overridden:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;client → db → collection → session → per-operation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Inheritance rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;General params (&lt;code class=&quot;language-text&quot;&gt;max&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;ttl&lt;/code&gt;) cascade down.&lt;/li&gt;
&lt;li&gt;If a lower level switches &lt;code class=&quot;language-text&quot;&gt;type&lt;/code&gt;, type-specific params reset to defaults (not inherited from parent).&lt;/li&gt;
&lt;li&gt;Session overrides collection — session is a logical grouping of operations, natural place to override runtime behavior (e.g., disable cache for a bulk import).&lt;/li&gt;
&lt;li&gt;Per-operation &lt;code class=&quot;language-text&quot;&gt;skipCache?: boolean&lt;/code&gt; is the most granular escape hatch.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;passing-a-cache-instance&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#passing-a-cache-instance&quot; aria-label=&quot;passing a cache instance permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Passing a cache instance&lt;/h4&gt;
&lt;p&gt;Users can provide either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Settings&lt;/strong&gt; — Pongo creates and manages the cache provider.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A pre-built cache provider instance&lt;/strong&gt; — Pongo uses it directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If settings are the same across multiple collections, Pongo can reuse the same provider instance internally. When a user passes an instance, multiple collections can explicitly share one cache.&lt;/p&gt;
&lt;h4 id=&quot;defaults&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#defaults&quot; aria-label=&quot;defaults permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Defaults&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enabled by default&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;max&lt;/code&gt;: follow &lt;code class=&quot;language-text&quot;&gt;lru-cache&lt;/code&gt; recommended defaults (1000)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;ttl&lt;/code&gt;: follow &lt;code class=&quot;language-text&quot;&gt;lru-cache&lt;/code&gt; recommended defaults&lt;/li&gt;
&lt;li&gt;Default provider: in-memory (&lt;code class=&quot;language-text&quot;&gt;lru-cache&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;integration-points&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#integration-points&quot; aria-label=&quot;integration points permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Integration points&lt;/h3&gt;
&lt;h4 id=&quot;where-pongocollection-factory-function&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#where-pongocollection-factory-function&quot; aria-label=&quot;where pongocollection factory function permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Where: &lt;code class=&quot;language-text&quot;&gt;pongoCollection&lt;/code&gt; factory function&lt;/h4&gt;
&lt;p&gt;Cache logic is added directly inside &lt;code class=&quot;language-text&quot;&gt;pongoCollection&lt;/code&gt;, not as an external decorator/wrapper. This gives cache operations access to internal state (filter inspection, &lt;code class=&quot;language-text&quot;&gt;_version&lt;/code&gt;, write outcomes) and keeps observability precise.&lt;/p&gt;
&lt;h4 id=&quot;read-operations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#read-operations&quot; aria-label=&quot;read operations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Read operations&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;findOne&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the filter targets &lt;code class=&quot;language-text&quot;&gt;_id&lt;/code&gt;, check cache first.&lt;/li&gt;
&lt;li&gt;Cache hit → return cached document.&lt;/li&gt;
&lt;li&gt;Cache miss → query DB, populate cache, return.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;skipCache?: boolean&lt;/code&gt; option available.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;findMany&lt;/code&gt; / other query methods:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the filter is a list of &lt;code class=&quot;language-text&quot;&gt;_id&lt;/code&gt; values, check cache for each.&lt;/li&gt;
&lt;li&gt;Return cached hits, query DB for misses, populate cache with DB results.&lt;/li&gt;
&lt;li&gt;Non-&lt;code class=&quot;language-text&quot;&gt;_id&lt;/code&gt; filters bypass cache entirely (cache is by-id only).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;write-operations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#write-operations&quot; aria-label=&quot;write operations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Write operations&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;insertOne&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After successful insert, put the document into cache.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;insertMany&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After successful insert, put all documents into cache.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;updateOne&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;updateMany&lt;/code&gt; / &lt;code class=&quot;language-text&quot;&gt;replaceOne&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After successful write, update the cache entry with the new document state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;deleteOne&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After successful delete, evict from cache.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-text&quot;&gt;deleteMany&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If deleting by ids, evict those ids from cache.&lt;/li&gt;
&lt;li&gt;Filter-based &lt;code class=&quot;language-text&quot;&gt;deleteMany&lt;/code&gt; does not evict (no way to know affected ids without extra query). Future improvement possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;optimistic-concurrency&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#optimistic-concurrency&quot; aria-label=&quot;optimistic concurrency permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Optimistic concurrency&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;On concurrency error (version mismatch), &lt;strong&gt;evict the stale entry&lt;/strong&gt; from cache. This is critical — a concurrency error means someone else updated the record (e.g., from another node), so the cached version is stale.&lt;/li&gt;
&lt;li&gt;If a caller provides &lt;code class=&quot;language-text&quot;&gt;expectedVersion&lt;/code&gt; and the cached document has &lt;code class=&quot;language-text&quot;&gt;_version&lt;/code&gt;, compare them in memory. If they don’t match, we know it’s stale without hitting the DB — cheap short-circuit.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;handle-method&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#handle-method&quot; aria-label=&quot;handle method permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;handle&lt;/code&gt; method&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Accepts &lt;code class=&quot;language-text&quot;&gt;id: string | string[]&lt;/code&gt; (overloaded: string returns single result, string[] returns array).&lt;/li&gt;
&lt;li&gt;Read phase uses cache by default.&lt;/li&gt;
&lt;li&gt;Write phase updates/evicts cache based on outcome.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;skipCache?: boolean&lt;/code&gt; available in handle options.&lt;/li&gt;
&lt;li&gt;For batch: loads all ids from cache, fetches misses from DB in one query, processes handler per document, batch writes to DB, updates cache after commit.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;transactions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#transactions&quot; aria-label=&quot;transactions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Transactions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Writes update cache only after commit.&lt;/strong&gt; Not during the transaction — uncommitted data should not enter the cache.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reads within a transaction check cache&lt;/strong&gt; by default (most transactions are short-lived, cached data is probably valid). If the transaction modified a document and then reads it, cache miss falls through to DB which returns the correct uncommitted state.&lt;/li&gt;
&lt;li&gt;Cache can be disabled per-session: &lt;code class=&quot;language-text&quot;&gt;startSession({ cache: &apos;disabled&apos; })&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Per-session cache settings follow the cascade (session overrides collection).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;error-handling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#error-handling&quot; aria-label=&quot;error handling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Error handling&lt;/h3&gt;
&lt;p&gt;Cache provider errors are &lt;strong&gt;swallowed&lt;/strong&gt; — treated as cache misses. The application never fails because of a cache failure.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cache is an optimization, not a correctness requirement.&lt;/li&gt;
&lt;li&gt;Errors trigger &lt;code class=&quot;language-text&quot;&gt;onError&lt;/code&gt; hook for debugging.&lt;/li&gt;
&lt;li&gt;Future: errors will become metrics and trace events when Pongo’s observability layer lands.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;future-considerations-out-of-scope-for-v1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#future-considerations-out-of-scope-for-v1&quot; aria-label=&quot;future considerations out of scope for v1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Future considerations (out of scope for v1)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cache warming&lt;/strong&gt;: pluggable strategy for pre-populating cache on startup. Interface hook defined but no default implementation shipped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query-result caching&lt;/strong&gt;: caching results of non-&lt;code class=&quot;language-text&quot;&gt;_id&lt;/code&gt; queries. Complex invalidation — deferred.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-node cache sharing&lt;/strong&gt;: Redis/Memcached providers. The interface supports it; no provider shipped in v1.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filter-based &lt;code class=&quot;language-text&quot;&gt;deleteMany&lt;/code&gt; eviction&lt;/strong&gt;: would require querying affected ids before delete.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observability integration&lt;/strong&gt;: metrics (hit ratio, latency saved, eviction counts), trace attributes, log entries for cache operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;implementation-approach&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#implementation-approach&quot; aria-label=&quot;implementation approach permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Implementation approach&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Define &lt;code class=&quot;language-text&quot;&gt;PongoCacheProvider&lt;/code&gt; interface and &lt;code class=&quot;language-text&quot;&gt;CacheConfig&lt;/code&gt; types.&lt;/li&gt;
&lt;li&gt;Implement in-memory provider using &lt;code class=&quot;language-text&quot;&gt;lru-cache&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Build Pongo cache wrapper (key prefixing, event hooks, error swallowing).&lt;/li&gt;
&lt;li&gt;Wire cascading config resolution (client → db → collection → session → per-op).&lt;/li&gt;
&lt;li&gt;Integrate into &lt;code class=&quot;language-text&quot;&gt;pongoCollection&lt;/code&gt;: intercept &lt;code class=&quot;language-text&quot;&gt;_id&lt;/code&gt;-based reads, update cache on writes, evict on deletes and concurrency errors.&lt;/li&gt;
&lt;li&gt;Extend &lt;code class=&quot;language-text&quot;&gt;handle&lt;/code&gt; to accept &lt;code class=&quot;language-text&quot;&gt;string | string[]&lt;/code&gt; with batch support.&lt;/li&gt;
&lt;li&gt;Add &lt;code class=&quot;language-text&quot;&gt;skipCache&lt;/code&gt; to operation options.&lt;/li&gt;
&lt;li&gt;Tests: unit tests for cache provider, integration tests for cache + collection operations, e2e tests for concurrency scenarios.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s.&lt;/strong&gt; and yes &lt;a href=&quot;https://gemini.google/pl/overview/image-generation/&quot;&gt;Nano Banana&lt;/a&gt; also did pretty well generating the cover of this post!&lt;/p&gt;
&lt;p&gt;p.s.2. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The End of Coding? Wrong Question]]></title><description><![CDATA[Be careful what you wish for, because your wish may come true. What LLMs revealed is how many people in our industry don’t like to code. It…]]></description><link>https://event-driven.io/en/the_end_of_coding_wrong_question/</link><guid isPermaLink="false">https://event-driven.io/en/the_end_of_coding_wrong_question/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8dda12a6f19358ea86de7c87dfa9d46c/a331c/2026-03-09-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADVElEQVR42lWTy28aVxTGR0ns2GDehgCGgWGGYYABbGwwNjY2BBs7xQ8wfsR5tk76cB9ZxGmlRF61UdVHVKVdVO0msrLxIlKlZlGpVVV10UXTbrqsVFXqP9B/4dcLrLr4dM69V/c73zn3u1KolMNfyhIomLizCWLFFPWqSbmkYeRV3HoUSzSMLRYViGBRwlhUGYvIrVoUa1zBmdJxmQaeXBJJqU4TXSzgnx3HrJd5+vn7/Pr8lG+fPObtGxWWG+O4tAiBXJaV3W0udlrUtgU6m0w26iyJPU8miT2pMTqRRsq0lkg2F5EF8YOj6/x2+jE/nnzEDycPudyeIRB0YL/gxmcK5VstplcbrF2/wvbtAyqtdRr7u8y3N5Cn87iyBlLt9hVKVzdxyEHmJmMUcxoHew3uvrKI12tFkiTODQ8xJhTWO22KK8vUdzuU1y5R7WxRbK5QaW+iz8/iMBNILx8f07xzSHOnyfFrbZKan/liknbDxDI8gNM6yIBlCH82w8LGGuWmIBJK7WqEs8EA9kScYU3Bbuii9RTSp09OefDFV1y7ucmb7WkeHbW4/2qDQi5EZTLCbiXJgNtJeGqCmlCytNNh//B1MbtOr2WPeAxXKoEzLaKA9PPvf/Ls+1+4trdOa0Zhdy1HfjyAGvdSzsnocoDBUBCfqD5xsUq6MkdmsSJimdR8GVdSx66rOBJaD9Jf//zLiz/+5t2jQ5ZnwkzkQxhpPyHZxcDQOQZ8XhyG1rPNeXmMoUioD2Gf4WhItKz2znuEIkrPf3rByTffce/DT1jYeomoNkos5iahedCm0sKHKjY1il3MySlypy4u9hQJxGMijwmFfTgMFen+Z49556GwyHtHlHfWhe+yXKoa3LpzwNKNy3gMhVAxy6iYlU1VGOkaXJi5S9Bdd0ntXYgiI4qMtH33LWo3d4QtEowlFQq1Inu3rvLB119izE7hNTX0hSKp5TLxaolgISPmFsemxfrk3RfW+oQ2TSjUF/KMGmGkwTMYpTR+fYzK6ipvHN/DqYbxpVWUmRzxhSnMRhlzdY4LaQ1rNNInFCq7pLauyoSOZJdHsQXdnDl/lrmNKnohRUa4vrG/iTXi710OT6aIiP8uF01RXMEa8jEi/rdVifyfUI/xH/xeqWPyoEK+AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/8dda12a6f19358ea86de7c87dfa9d46c/507e6/2026-03-09-cover.webp 200w,
/static/8dda12a6f19358ea86de7c87dfa9d46c/28a80/2026-03-09-cover.webp 400w,
/static/8dda12a6f19358ea86de7c87dfa9d46c/8d2ea/2026-03-09-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/8dda12a6f19358ea86de7c87dfa9d46c/36ca5/2026-03-09-cover.png 200w,
/static/8dda12a6f19358ea86de7c87dfa9d46c/a3397/2026-03-09-cover.png 400w,
/static/8dda12a6f19358ea86de7c87dfa9d46c/a331c/2026-03-09-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/8dda12a6f19358ea86de7c87dfa9d46c/a331c/2026-03-09-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Be careful what you wish for, because your wish may come true.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What LLMs revealed is how many people in our industry don’t like to code.&lt;/p&gt;
&lt;p&gt;It’s intriguing that now they claim and showcase what they “built with Claude”, whereas usually that means they generated a PoC.&lt;/p&gt;
&lt;p&gt;It’s funny, as people still focus on how they’re building, so it’s all about the code. And if that’s the message sent outside, together with the thought that LLMs are already better than “average coder Joe”, then the logical follow-up question is: why do we need those humans in the loop?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I think that most people look at the forest and see trees.&lt;/strong&gt; The current way of working with LLMs is not scalable. It’s transition phase. I can’t imagine calling myself an engineer and doing ONLY &lt;a href=&quot;https://web.archive.org/web/20200217080706/http://blog.codinghorror.com/new-programming-jargon/&quot;&gt;stringly-typed&lt;/a&gt; development with chat or markdown.&lt;/p&gt;
&lt;p&gt;What I can imagine is getting help from it, and using those tools as a help for research, for generating the OUTPUT, still keeping me responsible for the OUTCOME.&lt;/p&gt;
&lt;p&gt;Why am I saying that we’re in the transition phase? As prompting in our natural language is not precise, it’s verbose, and adding a translation layer between our freeform prompts and programming languages is a waste of time (and tokens, which LLM vendors love).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I think that we’ll still be coding, but with some other layer, as LLMs are good with structured input, like programming languages.&lt;/strong&gt; So we might need other programming languages than we have atm. Might we need different tools to evaluate LLMs’ output to make it deterministic? Might we need a different approach for engineering to make it scalable? Might we need more?&lt;/p&gt;
&lt;p&gt;Still, I don’t see those discussions.&lt;/p&gt;
&lt;p&gt;I mostly see: noise and celebrities who don’t code showing their beautiful PoCs. And doing a mic drop about the end of coding. Just like PoC represents the whole Software Development Life Cycle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, will we code or not?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If the answer is yes, let’s talk about what’s next, then let’s discuss how and when.&lt;/p&gt;
&lt;p&gt;If the answer is not, then let’s talk about how our job will change, ask if it’s still engineering, etc.&lt;/p&gt;
&lt;p&gt;Let’s try to make our discussions more precise, more focused on the essence, and avoid them from becoming just a bunch of anecdotal evidence.&lt;/p&gt;
&lt;p&gt;Noone outside our industry cares how we code unless it changes the cost, quality, or delivery time.&lt;/p&gt;
&lt;p&gt;Let’s discuss the impact that matters, rather than just the amount of code we produce (or not).&lt;/p&gt;
&lt;p&gt;Now, guess who I am quoting:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Imagine you’re a software application developer. Your programming language of choice (or the language that’s been foisted on you) is Java or Typescript. You’ve been at this for quite a while and your job doesn’t seem to be getting any easier.&lt;/p&gt;
&lt;p&gt;These past few years you’ve seen the growth of multiple incompatible architectures. Now you’re supposed to cope with all this and make your applications work in a distributed client-server environment. The growth of the Internet, the World-Wide Web, and “electronic commerce” have introduced new dimensions of complexity into the development process.&lt;/p&gt;
&lt;p&gt;The tools you use to develop applications don’t seem to help you much. You’re still coping with the same old problems; the fashionable new object-oriented techniques seem to have added new problems without solving the old ones.&lt;/p&gt;
&lt;p&gt;You say to yourself and your friends, “There has to be a better way”!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Better Way is Here Now&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now there is a better way—it’s our new model. Imagine, if you will, this development world…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s still dead simple.&lt;/li&gt;
&lt;li&gt;Your development cycle is much faster.&lt;/li&gt;
&lt;li&gt;Your applications can be created across multiple platforms. Write your spec once, and you never need to port them—they will be recreated if you without your hand-rolled modification on multiple operating systems and hardware architectures.&lt;/li&gt;
&lt;li&gt;Your applications are adaptable to changing environments.&lt;/li&gt;
&lt;li&gt;Your end users can trust that your applications are secure, and you can use protection against viruses and tampering through security scans.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You don’t need to dream about these features. They’re here now.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And also this from another source:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When I started interviewing programmers in 2005, I would generally let them use any language or tool they wanted to solve the coding problems I gave them. 99% of the time, they chose Java.&lt;/p&gt;
&lt;p&gt;Nowadays, they tend to choose LLMs.&lt;/p&gt;
&lt;p&gt;Now, don’t get me wrong: there’s nothing wrong with LLN as an implementation tool.&lt;/p&gt;
&lt;p&gt;Wait a minute, I want to modify that statement. I’m not claiming, in this particular article, that there’s anything wrong with LLM as an implementation tool. There are lots of things wrong with it but those will have to wait for a different article.&lt;/p&gt;
&lt;p&gt;Instead what I’d like to claim is that LLM is not, generally, a hard enough programming tool that it can be used to discriminate between great programmers and mediocre programmers. It may be a fine tool to work in, but that’s not today’s topic. I would even go so far as to say that the fact that LLMs aere not hard enough is a feature, not a bug, but it does have this one problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, I cheated you, but only a bit. I changed “Java” to “LLM ” and cut some phrases.&lt;/p&gt;
&lt;p&gt;The first one is from &lt;a href=&quot;https://www.stroustrup.com/1995_Java_whitepaper.pdf&quot;&gt;“The Java Language Environment”&lt;/a&gt; by Sun Microsystems, introducing Java in 1995.&lt;/p&gt;
&lt;p&gt;The second one was from Joel Spolsky’s &lt;a href=&quot;https://www.joelonsoftware.com/2005/12/29/the-perils-of-javaschools-2/blog&quot;&gt;“The Perils of JavaSchools”&lt;/a&gt; article, written in 2001.&lt;/p&gt;
&lt;p&gt;Let me be clear. I’m not trying to do grandpa talk on the old days and claim that it’s the same old thing.&lt;/p&gt;
&lt;p&gt;What I’m trying to say is that we were continuously introducing new abstractions into our development cycle to scale it. By scale, I mean: getting more people to deliver more code. Even Java was invented for precisely this goal. Yes, the one that’s together with craftsmanship madness presented as “the enterprisy complex environment”. In the early days, it was just said that it’ll make us dumber.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal of abstraction is not to gatekeep but to allow us to reduce cognitive load.&lt;/strong&gt; We invented new languages to help us, but then added more components like a distributed environment, multiregion, because of globalisation. For the same reasons, we use cloud-native tooling so we don’t have to deal with it.&lt;/p&gt;
&lt;p&gt;Some of the architecture and security tools were commoditised by the cloud. We don’t need to think about much stuff we had to do before.&lt;/p&gt;
&lt;p&gt;Will Claude-native do the same?&lt;/p&gt;
&lt;p&gt;We don’t need to learn Assembler, C++, Lisp anymore; we have lost a lot of mechanical sympathy. We deal with higher abstractions, but we still engineer solution, we still code, is it a different coding? It is. Is it better or worse? Well, it is how it is. As Gerald Weinberg said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Things are the way they are because they got that way&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And now the question is whether we’re fine with the way we’re doing stuff, and where we’re getting to.&lt;/p&gt;
&lt;p&gt;Personally, I don’t think that stringly-typed markdown or chat-based design will be the thing in the future.&lt;/p&gt;
&lt;p&gt;Knowing how skilled we were always with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;breaking down tasks into smaller chunks,&lt;/li&gt;
&lt;li&gt;writing precisely what we had in mind,&lt;/li&gt;
&lt;li&gt;thinking before doing,&lt;/li&gt;
&lt;li&gt;waging tradeoffs,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m optimistic about the Spec-Driven Design idea. It’s going to be great.&lt;/p&gt;
&lt;p&gt;Not.&lt;/p&gt;
&lt;p&gt;If we want to call ourselves engineers, we need to put more structure and determinism.&lt;/p&gt;
&lt;p&gt;I agree that reviewing all code generated by the GenAI is not sustainable.&lt;/p&gt;
&lt;p&gt;But I also don’t think that generating tons of code is, in general, sustainable.&lt;/p&gt;
&lt;p&gt;With the current state of the art we have, sure, that’s some solution to just generate based on the tools we have.&lt;/p&gt;
&lt;p&gt;But let’s start to think what’s next.&lt;/p&gt;
&lt;p&gt;Simon Wardley &lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7426977059677077504/&quot;&gt;brought an interesting point&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;That said, it is fine for an entire culture to decide that producing outputs matters more than understanding mechanisms. You only have to compare the practical engineering of the Roman Empire and the loss of inquiry from science in the Hellenistic age to see this. When the Roman Empire collapsed, the practical knowledge embedded in those institutions (how to maintain aqueducts, how to produce certain grades of concrete) was lost remarkably quickly. Not because people decided to forget it, but because the knowledge was procedural, embedded in chains of practice rather than recorded as transferable understanding. When the chains of practice broke, that embedded knowledge went with them. We had to rediscover the art of inquiry (i.e. Science) to bring them back.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So if we don’t code, how do we hone our skills? How will newcomers from LLMSchools (using Joel’s terms) be able to decide whether something is wrong or right? I don’t think that you can be good on something without doing it.&lt;/p&gt;
&lt;p&gt;The paradox with code is that it’s not an asset; it’s a liability. Some say that code doesn’t matter, only proper design. But how do you define &lt;em&gt;“proper design”&lt;/em&gt;? Yes, code style doesn’t matter as long as it works as expected. But…&lt;/p&gt;
&lt;p&gt;But code eventually matters, as that’s the source of truth for what’s on production. As Alberto Brandolini said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It’s developers’ (mis)understanding, not domain experts’ knowledge, that gets released in production.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now, it’s the developers’ and LLMs’ misunderstandings that are deployed to production, not the expert’s knowledge. Neither the markdown spec.&lt;/p&gt;
&lt;p&gt;And coding is just one danger.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Outsourcing thinking is an even more dangerous path, as:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If LLMs are doing everything, then what are humans for? Aren’t we cutting the branch on which we’re sitting?&lt;/li&gt;
&lt;li&gt;LLMs are statistical parrots. They repeat the most possible answer. Which means mediocre. This can still be fine enough for many cases, but for those we want to make a difference for? Definitely not.&lt;/li&gt;
&lt;li&gt;Just like we’re losing our coding skills by not doing them, we’re losing design skills by not practising them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, whatever happens, LLMs will stay with us. How and where it’s hard to say. Unless you have a Magic 8 Ball of 100% correct predictions. I don’t.&lt;/p&gt;
&lt;p&gt;That’s why I’d like our industry to finally start mature discussions on the real impact. I would like us to stop acting like children, bragging about generating code and then claiming that code doesn’t matter.&lt;/p&gt;
&lt;p&gt;I’d like to think about how to reshape our SDLC process and make it sustainable.&lt;/p&gt;
&lt;p&gt;I’d like us to think about what tools we need, and how to change what we have.&lt;/p&gt;
&lt;p&gt;If we won’t finally start to do it, then things will be the way they are because they got that way.&lt;/p&gt;
&lt;p&gt;And it might not be what we wished for.&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s.&lt;/strong&gt; to kinda prove that I’m more sceptic and pragmatic than hater, I recently started playing with building an Agent with Emmett to better understand those tools. If you’d like to read about the findings and honest thoughts I have while doing it, please comment!&lt;/p&gt;
&lt;p&gt;p.s.2. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Parse, Don't Guess]]></title><description><![CDATA[Last time, I shared with you how sneaky I was on transaction handling.. Today, the opposite: I’ll tell you how I fixed the issue when I…]]></description><link>https://event-driven.io/en/parse_dont_guess/</link><guid isPermaLink="false">https://event-driven.io/en/parse_dont_guess/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9612dd92364aaa25830c836976efdda9/8537d/2026-03-03-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABwUlEQVR42l2S227TQBCG/f4PwQ1VoaVqEQUhNRIqNzwAqJDEhzj2+rxr79pxmru//y5x6+Ti0xw9Mztj73nXYa8bjKrA2FZ4HlqijrQ4HO0D83amgWZOr+tX/zneyKDertGFT9DxCru2xGikY99LmK5yuqE/SQNEzImTAErm9DevuROeKTZQ8RJDk2KoU/S0R11xkhJZHqEoYqfbYtvEh2wERBbBj/6hZ7O9K/qGKyg5XZf6sLrOQj69hKwF1vRnIoJWOVIWDMK/tEMIEmyWMHZNLLqb4XVMbPzf0HlIInQiQM/n2ODBdj0mVmxUMLfjhA2nLqn3LDiw+RzPMMHwqQ7qurIyg2FRTanlf93FaNtmRgpn24LnuILa7imPXdG9thOV7jhDW7h9OpvMdRszrtkpnmaRia5KsFn9Qcb9ZLxmzV1tgyenpzxCwh0WPIxg3ErNQ54iuMM6gcU6ZBlj8f0L7j5d4v72Ix4X3yivcHt9gV8/F7i/u8bnmw+4fP8OPx6+Yvp2jtdWW8wZeFFVRGiEjzJZQWYBqmTppOLh6nQNSWk4QFuefmvxFKeaI3lpaf9Ny9GueWErJ5/izuWkn/ECihItXZBfLdMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/9612dd92364aaa25830c836976efdda9/507e6/2026-03-03-cover.webp 200w,
/static/9612dd92364aaa25830c836976efdda9/28a80/2026-03-03-cover.webp 400w,
/static/9612dd92364aaa25830c836976efdda9/8d2ea/2026-03-03-cover.webp 800w,
/static/9612dd92364aaa25830c836976efdda9/68fc1/2026-03-03-cover.webp 1200w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/9612dd92364aaa25830c836976efdda9/36ca5/2026-03-03-cover.png 200w,
/static/9612dd92364aaa25830c836976efdda9/a3397/2026-03-03-cover.png 400w,
/static/9612dd92364aaa25830c836976efdda9/a331c/2026-03-03-cover.png 800w,
/static/9612dd92364aaa25830c836976efdda9/8537d/2026-03-03-cover.png 1200w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/9612dd92364aaa25830c836976efdda9/a331c/2026-03-03-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/en/cloudflare_d1_transactions_and_tradeoffs/&quot;&gt;Last time, I shared with you how sneaky I was on transaction handling.&lt;/a&gt;. Today, the opposite: I’ll tell you how I fixed the issue when I tried to be too sneaky. I already told you that &lt;a href=&quot;/en/sneaky-code-bites-back/&quot;&gt;Sneaky Code Bites Back&lt;/a&gt;. The moral? Do as I tell, not how I do.&lt;/p&gt;
&lt;p&gt;In some environments, we’re spoiled. We’re getting a lot from a Base Class Library or standard frameworks, so we stop thinking that those issues can exist. For instance, serialisation. Do you know how many data types JSON has? 6. Six. Sześć.&lt;/p&gt;
&lt;p&gt;Exactly those:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;string,&lt;/li&gt;
&lt;li&gt;number,&lt;/li&gt;
&lt;li&gt;boolean,&lt;/li&gt;
&lt;li&gt;object,&lt;/li&gt;
&lt;li&gt;array,&lt;/li&gt;
&lt;li&gt;and (TADA!) null.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What about number precision and size? It is. That’s what I can tell you, but it’s not enough, e.g., to keep big int/long, etc. What about Dates? Also, there are none. I wrote about it longer &lt;a href=&quot;/en/fun_with_json_serialisation/&quot;&gt;here or how much fun that brings&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you use statically typed languages and runtimes like C#, Java, etc., your serialiser can, in addition to parsing, perform mapping and, sometimes, validation. And it can also be tricky, as nicely &lt;a href=&quot;https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/&quot;&gt;Alexis King put in her “Parse, don’t validate”&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you’re in a dynamic environment, like JavaScript, then you’re left with parsing and explicit mapping afterwards. What about TypeScript? Same case, types are only used during compilation, then erased and not visible at runtime. So, the place where we do parsing.&lt;/p&gt;
&lt;p&gt;Because JSON was defined a long time ago, JavaScript moved on and now supports bigints (Big Integers) and Dates natively (what an achievement!), which created a gap I wanted to fill.&lt;/p&gt;
&lt;p&gt;As you know from my previous articles (e.g. &lt;a href=&quot;/en/checkpointing_message_processing/&quot;&gt;this one&lt;/a&gt;), big integers are quite important in distributed processing. You can represent the position in log with them. Since your log may be quite long, regular numbers aren’t enough. Or they’re long enough, until they overflow, then they’re not anymore.&lt;/p&gt;
&lt;p&gt;I’m using those bigint types extensively in internals in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; and &lt;a href=&quot;https://github.com/event-driven-io/pongo&quot;&gt;Pongo&lt;/a&gt;. And I store them in JSONs. I store them as alphanumeric strings, because strings don’t have a max length (or at least I don’t know such).&lt;/p&gt;
&lt;p&gt;So, for instance, an event payload can look like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;InvoiceIssued&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;data&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;invoiceNumber&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;123&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;issuer&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;John Doe&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;issuedAt&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2026-02-23T14:07:20Z&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;metadata&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;streamPosition&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;globalPosition&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;928391&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see in the metadata stream and global positions, the values are bigints (even if they’re smaller than the maximum value), and data can also use bigints if the user decides to (e.g., invoice number).&lt;/p&gt;
&lt;p&gt;And encoding data is simple: you convert it to a string, call it a day. But how to get it back?&lt;/p&gt;
&lt;p&gt;And here’s where my struggles started. How do you know that someone intentionally used bigint when they just wanted to store a number as a string?&lt;/p&gt;
&lt;p&gt;There are several options. The first one is: encode value.&lt;/p&gt;
&lt;p&gt;We could store it, for instance, as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prefixed value: &lt;strong&gt;“_bigint:928391”&lt;/strong&gt;. But then you need to find a prefix that will be unique enough not to cause conflicts,&lt;/li&gt;
&lt;li&gt;nested object, e.g. &lt;strong&gt;{ “_kind”: “bigint”, value: “928391” }&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, either based on the prefix or the object structure, we could automatically decode the value. Still, ther creates other issues, as the structure no longer matches the original value. If we’re just storing and retrieving, that shouldn’t be so bad, but… But remember that in &lt;a href=&quot;https://github.com/event-driven-io/pongo&quot;&gt;Pongo&lt;/a&gt; I’m allowing the use of PostgreSQL and SQLite as document databases, supporting such queries:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; invoices &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoDb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Invoice&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;invoices&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; invoiceNumber &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123n&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; invoice &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; invoices&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; invoiceNumber &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That gets translated into a &lt;a href=&quot;/en/postgresql-jsonb-powerful-storage/&quot;&gt;fancy JSONB SQL query&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course, I could work around it by encoding the value, but… But I was lazy!&lt;/p&gt;
&lt;p&gt;I decided to use a Get Out of Jail Free Card and just treat all strings with numbers as bigints. Sneaky. And it will get even sneakier.&lt;/p&gt;
&lt;p&gt;In JavaScript, JSON.parse accepts a parameter that allows you to provide custom mapping logic. I decided to use it and check if the string is alphanumeric, and gulp, I’ve used a regular expression to parse it:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; bigIntReviver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;JSONReviver&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;string&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;^[+-]?\d+n?$&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;BigInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Yes, it’s either DNS or Regex. Or both.&lt;/p&gt;
&lt;p&gt;I &lt;a href=&quot;/en/typescript-migrates-to-go-whats-really/&quot;&gt;explained in another article&lt;/a&gt; that JavaScript runtime doesn’t like where you do CPU-heavy computations.&lt;/p&gt;
&lt;p&gt;Small Regex isn’t CPU-heavy, but if you consider that ther will be done for each string in each document or event you try to deserialise, and multiply that by the number of concurrent requests? That can cause the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model&quot;&gt;JavaScript event loop to freeze&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What’s more, I plugged that automatically into &lt;a href=&quot;https://node-postgres.com/&quot;&gt;node-postgres driver&lt;/a&gt; custom type handling, so each JSONB deserialization goes through it.&lt;/p&gt;
&lt;p&gt;Again, not shit, Sherlock. I should have known it wasn’t the best choice, but I was busy trying to be sneaky at that moment.&lt;/p&gt;
&lt;p&gt;Fortunately, a user, Dawid, benchmarked and noticed CPU freezes. It wasn’t catastrophic, but clearly needed a fix.&lt;/p&gt;
&lt;h2 id=&quot;the-shift&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-shift&quot; aria-label=&quot;the shift permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Shift&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;And here I had several options.&lt;/strong&gt; I could keep hacking on the same idea- maybe replace the Regex with a simpler string check, still globally. Or I could just ignore bigints during deserialisation entirely, let them stay strings, call it a day, move on. Or I could apply the encoding I mentioned earlier, prefixed values, and nested objects. All of those would fix the performance issue. And all of those would be the same kind of wrong choice I already made: trying to solve a schema problem without the schema.&lt;/p&gt;
&lt;p&gt;Because that’s the actual mistake here, not the Regex. The &lt;em&gt;pg&lt;/em&gt; driver has no idea what your schema looks like. It doesn’t know that &lt;em&gt;“928391”&lt;/em&gt; is a bigint and &lt;em&gt;“John Doe”&lt;/em&gt; is a name. It doesn’t know that &lt;em&gt;“123”&lt;/em&gt; is an invoice number (bigint!) and &lt;em&gt;“90210”&lt;/em&gt; is a zip code (string!). I asked it to guess, and it guessed wrong, because there is no right guess at that level.&lt;/p&gt;
&lt;p&gt;Enough is enough. I had been planning to do ther properly for a while, and the performance issue gave me the push I needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast&quot;&gt;Old rule says: “Make it work, make it right, make it pretty”.&lt;/a&gt;&lt;/strong&gt; I had &lt;em&gt;“make it work”&lt;/em&gt; covered for a long time. Now it was time for &lt;em&gt;“make it right”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And honestly? It wasn’t that hard. Maybe because “make it work” came first, I already understood the problem well enough to see the shape of the solution.&lt;/p&gt;
&lt;p&gt;In &lt;a href=&quot;https://github.com/event-driven-io/Pongo/pull/149&quot;&gt;Pongo&lt;/a&gt;, I dropped the automatic bigint parsing from the driver entirely. If you want bigint or date parsing, you say so at the client level:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  driver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; databaseDriver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  connectionString&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; postgresConnectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  serialization&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      parseBigInts&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      parseDates&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By default, strings stay strings. You opt in. I didn’t want to break things for users who don’t care about bigint precision or don’t have performance-sensitive workloads. The serializer became an explicit parameter passed down to each query, each collection, each operation- instead of a global that silently changed everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That was the “make it right” part for Pongo.&lt;/strong&gt; But disabling alone isn’t a solution, it’s a band-aid. Users who need bigints and dates still need a way to get them back after deserialisation. The question is: where does that conversion happen?&lt;/p&gt;
&lt;p&gt;And that’s where upcasting comes in. Let me start with a simple example in &lt;a href=&quot;https://github.com/event-driven-io/Pongo/pull/149&quot;&gt;Pongo&lt;/a&gt;, then build up.&lt;/p&gt;
&lt;p&gt;Say you have a user document. In the database, dates are stored as ISO strings, and the version counter is a numeric string (because JSON). But in your application, you want proper Date objects and bigints:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserDocStored&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  lastLogin&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserDoc&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  lastLogin&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The upcast function does the conversion:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; upcast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UserDocStored&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UserDoc &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;createdAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  lastLogin&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastLogin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You wire it into the collection, and every read goes through it:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoDb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;UserDoc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; UserDocStored&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;users&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    schema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; versioning&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; upcast &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What’s in the database:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;&quot;createdAt&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2024-01-15T10:30:00.000Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ... &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What you get back:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Alice&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That’s all. &lt;em&gt;new Date(str)&lt;/em&gt; is cheap. Running a Regex against every string in the document is not. The CPU freeze Dawid spotted came from that check running millions of times at the driver level for every field on every concurrent request. With upcasting, the conversion runs only for the fields you declared, in a plain function, no Regex.&lt;/p&gt;
&lt;p&gt;But ther is just type mapping - the simplest case. As I wrote about &lt;a href=&quot;/en/fun_with_json_serialisation/&quot;&gt;in my serialisation article&lt;/a&gt;, the explicit mapping pattern is useful for much more than just fixing types. It’s the same pattern you need for schema versioning. It defines the stored schema and the application schema separately together with function to transform one into the other.&lt;/p&gt;
&lt;p&gt;Let’s say business requirements changed. You now need to group user data differently: a &lt;em&gt;profile&lt;/em&gt; object for identity data, and a &lt;em&gt;timestamps&lt;/em&gt; object for temporal data. The V1 documents are flat. The new V2 shape is nested:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserDocV1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  lastLogin&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UserDocV2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  profile&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  timestamps&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    lastLogin&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Ther isn’t just a type change like string-to-Date anymore. The structure itself is different. Flat fields became nested objects, and field names moved into sub-objects. And you have thousands of V1 documents already stored. You can’t migrate them all at once (or don’t want to, because it’s risky, and some consumers might still expect V1). But your application now expects V2.&lt;/p&gt;
&lt;h2 id=&quot;compatibility-ftw&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#compatibility-ftw&quot; aria-label=&quot;compatibility ftw permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Compatibility FTW&lt;/h2&gt;
&lt;p&gt;Ther is where backward and forward compatibility come in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Backward compatibility&lt;/strong&gt; means: old data still works. V1 documents stored months ago need to be readable by the V2 code. The upcast handles ther. It reads the document in whatever shape it has and transforms it into V2.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Forward compatibility&lt;/strong&gt; means: new data doesn’t break old consumers. If you have another service or an older deployment that still reads the V1 format, it needs to keep working. The downcast handles ther. When storing V2 documents, it writes the V1 fields alongside the V2 fields, so older readers can still find what they expect.&lt;/p&gt;
&lt;p&gt;Together:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StoredPayload&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; UserDocV1 &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; UserDocV2&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; upcast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StoredPayload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UserDocV2 &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  profile&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;profile &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  timestamps&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamps&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;createdAt &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;createdAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    lastLogin&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamps&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;lastLogin &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastLogin&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; downcast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UserDocV2&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StoredPayload &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;profile&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamps&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;createdAt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toISOString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  lastLogin&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamps&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastLogin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toISOString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  profile&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;profile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  timestamps&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; doc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamps&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Look at the upcast: if the nested &lt;em&gt;profile&lt;/em&gt; or &lt;em&gt;timestamps&lt;/em&gt; fields exist (document was written by V2 code), it uses them. If they don’t exist (for the old V1 document), it falls back to the flat fields. One function handles both old and new documents: that’s backward compatibility.&lt;/p&gt;
&lt;p&gt;And look at the downcast: it writes &lt;em&gt;name&lt;/em&gt;, &lt;em&gt;createdAt&lt;/em&gt;, &lt;em&gt;lastLogin&lt;/em&gt; as flat string fields (V1 shape) alongside &lt;em&gt;profile&lt;/em&gt; and &lt;em&gt;timestamps&lt;/em&gt; (V2 shape). A service still reading V1 sees the flat fields and works fine. A service reading V2 sees the nested ones. That’s forward compatibility.&lt;/p&gt;
&lt;p&gt;You wire both into the collection:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoDb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;UserDocV2&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; StoredPayload&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;users&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    schema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; versioning&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; upcast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; downcast &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From here, your application code only deals with V2. The collection handles the translation in both directions:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; v2Doc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UserDocV2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  profile&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Alice&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  timestamps&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;2024-01-15T10:30:00.000Z&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    lastLogin&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;2024-06-20T14:45:00.000Z&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;v2Doc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What’s stored (downcasted, both shapes for compatibility):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;createdAt&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2024-01-15T10:30:00.000Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;lastLogin&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2024-06-20T14:45:00.000Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;profile&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;timestamps&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;createdAt&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2024-01-15T10:30:00.000Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;lastLogin&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2024-06-20T14:45:00.000Z&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then you can read it back with:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; doc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And get upcasted to V2 data in your application code:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;profile&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Alice&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;timestamps&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;createdAt&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2024-01-15T10:30:00.000Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;lastLogin&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2024-06-20T14:45:00.000Z&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Same collection, V1 and V2 documents coexisting. &lt;em&gt;insertMany&lt;/em&gt;, &lt;em&gt;replaceOne&lt;/em&gt;, &lt;em&gt;findOne&lt;/em&gt;:  all go through the upcast/downcast. No batch migration needed. You roll out the new code, and old documents are handled transparently.&lt;/p&gt;
&lt;p&gt;There’s another thing the downcast gives you: querying remains backwards-compatible. Because the downcast writes the flat V1 fields alongside the nested V2 ones, a query like &lt;em&gt;collection.findOne({ name: ‘Alice’ })&lt;/em&gt; still works even though V2 code doesn’t use &lt;em&gt;name&lt;/em&gt; directly anymore. The V1 field is there in the stored document. That matters if you have queries or indexes built against the old shape. They don’t break.&lt;/p&gt;
&lt;p&gt;Now, for events, ther matters even more. In event sourcing, stored events are immutable- the log is append-only, and you don’t modify what was already written. I wrote about &lt;a href=&quot;/en/simple_events_versioning_patterns/&quot;&gt;versioning patterns&lt;/a&gt; in more detail. The core idea is: your business evolves, your code evolves, your event schemas evolve, but the events in the store stay as they were. You can’t go back and rewrite them (well, you can, but you really shouldn’t). Upcasting is how you bridge the gap.&lt;/p&gt;
&lt;p&gt;For &lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/292&quot;&gt;Emmett&lt;/a&gt;, the same pattern works at the event store level. You define the stored shape (what JSON gives you from the database) and the application shape (what your code works with):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartOpenedFromDB&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartOpened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; openedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; loyaltyPoints&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartOpened&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartOpened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; openedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; loyaltyPoints&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; bigint &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And an upcast that handles each event type:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; upcast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartEventWithDatesAndBigInt &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartOpened&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; e &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ShoppingCartOpenedFromDB&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          openedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;openedAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          loyaltyPoints&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;BigInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loyaltyPoints&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; e &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ShoppingCartConfirmedFromDB&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          confirmedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;confirmedAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          totalCents&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;BigInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalCents&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ShoppingCartEventWithDatesAndBigInt&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You pass it when reading a stream:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; state &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; eventStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;aggregateStream&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  ShoppingCartState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ShoppingCartEventWithDatesAndBigInt
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  evolve&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; evolveState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  read&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; schema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; versioning&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; upcast &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or in a command handler:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;CommandHandler&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ShoppingCart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ShoppingCartEvent&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;initialState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  schema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; versioning&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; upcast&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; upcastDatesAndBigInt &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The difference with events is that you can’t update them in place. For documents, you have both directions: upcast on read, downcast on write. For events, upcasting is the main tool because the event store is append-only. Old events stay as they were written. But downcasting has its place too.&lt;/p&gt;
&lt;p&gt;Consider ther: you have a projection or a subscriber that was built months ago against the old event schema. Maybe it’s a read model that listens to &lt;em&gt;ShoppingCartOpened&lt;/em&gt; and expects &lt;em&gt;clientId&lt;/em&gt; as a flat string. But your current code evolved. Now &lt;em&gt;ShoppingCartOpened&lt;/em&gt; carries a &lt;em&gt;client&lt;/em&gt; object with &lt;em&gt;id&lt;/em&gt; and &lt;em&gt;name&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// What old subscribers expect&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartOpenedV1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartOpened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; openedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// What current code produces&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartOpenedV2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartOpened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; client&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; openedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Upcasting enables the current code to read older events. The ones stored with just &lt;em&gt;clientId&lt;/em&gt;. Downcasting helps old subscribers consume new events. It transforms the new &lt;em&gt;client&lt;/em&gt; object back into the flat &lt;em&gt;clientId&lt;/em&gt; they expect. Same principle as with documents, but especially important here because event subscribers often live in separate services or deployments that you can’t update all at once.&lt;/p&gt;
&lt;p&gt;And the same upcast function that started as simple type mapping &lt;em&gt;string → Date&lt;/em&gt;, &lt;em&gt;string → bigint&lt;/em&gt; handles ther structural change too. You just add another case to the switch:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartOpened&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; e &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; ShoppingCartOpenedV1&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      client&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Unknown&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      openedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;openedAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Old events get the &lt;em&gt;client&lt;/em&gt; object synthesised from the flat &lt;em&gt;clientId&lt;/em&gt;. New events already have it. The evolve function only deals with the V2 shape.&lt;/p&gt;
&lt;p&gt;And here’s where things started to click for me. I added upcasting to fix the bigint problem: explicit type mapping instead of a Regex. But the same mechanism, without any changes, also handles structural versioning. The simple &lt;em&gt;string → Date&lt;/em&gt; mapping from the first example is the same code path as the &lt;em&gt;clientId → client&lt;/em&gt; migration above. It’s one function, one place, one pattern for all of it: type coercion, field restructuring, schema migration.&lt;/p&gt;
&lt;h2 id=&quot;right-decisions-stack&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#right-decisions-stack&quot; aria-label=&quot;right decisions stack permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Right decisions stack&lt;/h2&gt;
&lt;p&gt;Right decisions stack. The Regex hack was blocking the slot where upcasting should have been all along. Once I removed it, the performance got fixed, and I got schema versioning on top. One fix created room for the next one, which created room for the next. That doesn’t happen when you keep patching around the same bad decision.&lt;/p&gt;
&lt;p&gt;Looking back, maybe the Regex wasn’t the wrong first move. The rule is &lt;em&gt;“make it work, make it right, make it pretty”&lt;/em&gt;, in that order. The Regex made it work. It had performance problems, but it still let me ship and learn where the real problem was. If I had tried to design the upcast/downcast system from scratch, without having lived with the Regex for a while, I might have over-engineered it or missed the connection to schema versioning entirely. The understanding came from living with the shortcut.&lt;/p&gt;
&lt;p&gt;Dawid raised a performance issue with Pongo projections, but the same Regex was running in Emmett as well. I could have fixed it in one place and called it a day. Instead, I used it as a push to do the thing I’d been planning anyway and applied it to both Pongo and Emmett to keep things consistent. Because I already understood the problem well enough, “make it right” turned out easier than I expected.&lt;/p&gt;
&lt;p&gt;You can recover from shortcuts. You should. But you also shouldn’t be afraid to take them in the first place, as long as you come back and do it properly.&lt;/p&gt;
&lt;p&gt;Full changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/event-driven-io/Pongo/pull/149&quot;&gt;Pongo PR #149&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/292&quot;&gt;Emmett PR #292&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How I cheated on transactions. Or how to make tradeoffs based on my Cloudflare D1 support]]></title><description><![CDATA[We’re being told that software design is the art of making tradeoffs. But… Are we taught how to make them? Not that it’s easy to teach…]]></description><link>https://event-driven.io/en/cloudflare_d1_transactions_and_tradeoffs/</link><guid isPermaLink="false">https://event-driven.io/en/cloudflare_d1_transactions_and_tradeoffs/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c405bbf9b7c3fdee1d4ebfff49a57d7f/a331c/2026-02-16-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAACOUlEQVR42pWTS08TYRSG+wfYQBQ1LhAt7liY4MLEDe5Qly5dmVgTFf0D/gIXgiuXEhOURU00xkQlBDHxwkWEcrHFtlympdOhM73RaTsz3+OhTQxjg5EvOZnLmfPkPe+cE6BxFAeff+VaT6Cl2KlA1ZLIN+/xDg9U9RLUi6hCEqVNoXYWUcY8Sp9FmXGoCdh1D6HQq6GsBK4eQVULzYynUMmPEHkKyQnICriU+0+Fdl6U/BIl+wq2V/FGr+KGr+G+v4OaeQwLr0XpXxYov8cBd++5kqWwPs+r8BiGYTQFZjVq4Yc4Q1ckLlIZvUF97q0foDw8c7Nx/QO0DB1r+TMTL0boOnKU4aFHjUR9K8buWgy77FAyimQMF3sjKYlqs9Kti+dpvHTENwmB6TfPGXtwn+HBe1w43c3oiHhGntraF4rL09TzOWzTIps2KUeXYNdoAuq22LSOl1nxtxzs7uJ8by/ngj0cO36S8LMnYsEKNS2OGZmlaFjkdRNNy2NFf0JZB1ugtTIqG0Xlkv6Wb4ZuCegEXd2nONNzlruh67D1DlXeoZCIsaUVSKVLpDZNKpmUADVpNYGqmAJcle90v8JL/f20tbVxeWCARDxO6PYgC5MvYXMKT1/B25jDiY5DQiK3LDO6hNrVZU5lxMwYyrH9wM7OTjo6OgiFQo0X3z5NMjP5Yc913NSS/G1RoS8K/LvAxwX0A7X9FWQJcKotqxlob28nGAzS19eHKebvrVollzl4gz2nZfb2A38DNA+vGzYxLJ0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/c405bbf9b7c3fdee1d4ebfff49a57d7f/507e6/2026-02-16-cover.webp 200w,
/static/c405bbf9b7c3fdee1d4ebfff49a57d7f/28a80/2026-02-16-cover.webp 400w,
/static/c405bbf9b7c3fdee1d4ebfff49a57d7f/8d2ea/2026-02-16-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/c405bbf9b7c3fdee1d4ebfff49a57d7f/36ca5/2026-02-16-cover.png 200w,
/static/c405bbf9b7c3fdee1d4ebfff49a57d7f/a3397/2026-02-16-cover.png 400w,
/static/c405bbf9b7c3fdee1d4ebfff49a57d7f/a331c/2026-02-16-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/c405bbf9b7c3fdee1d4ebfff49a57d7f/a331c/2026-02-16-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We’re being told that software design is the art of making tradeoffs. But… Are we taught how to make them?&lt;/p&gt;
&lt;p&gt;Not that it’s easy to teach tradeoffs, it’s a subtle art of explanation. You need to provide enough context and be precise so others don’t treat tradeoffs as a general best practice. Because they’re usually not such, they typically come from the muddy banks of the Wishkah River.&lt;/p&gt;
&lt;p&gt;I think that a decent way is to tell the story. Not the fairy tale, but the specific tradeoff applied in practice. That’s what I’m going to do today: tell you how I cheated on database transactions.&lt;/p&gt;
&lt;h2 id=&quot;dumbo&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dumbo&quot; aria-label=&quot;dumbo permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dumbo&lt;/h2&gt;
&lt;p&gt;Do you know Dumbo? Of course, flying elephant, such a sweetie. It’s also a codename of my Open Source project. I didn’t tell you about it so far, as it’s a shared dependency for &lt;a href=&quot;https://github.com/event-driven-io/pongo&quot;&gt;Pongo&lt;/a&gt; and &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt;, responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;connection pooling,&lt;/li&gt;
&lt;li&gt;safe connection lifetime management,&lt;/li&gt;
&lt;li&gt;handling transactions,&lt;/li&gt;
&lt;li&gt;migrations,&lt;/li&gt;
&lt;li&gt;SQL execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not that small a scope for a Dumb tool, aye? Still, the intention is to make usage dumb, hiding the weirdness of a specific SQL database, so I solve it once and don’t need to be constantly distracted thinking about it, but focus on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to append and process events the best way in Emmett,&lt;/li&gt;
&lt;li&gt;How to make the best JSON handling and translation into specific SQL dialects in Pongo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dumbo usage is quite simple:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; dumbo &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/dumbo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pgDumboDriver &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/dumbo/pg&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dumbo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; driver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; pgDatabaseDriver &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You need to set up a specific database driver (e.g. &lt;em&gt;pg&lt;/em&gt; for PostgreSQL or &lt;em&gt;sqlite3&lt;/em&gt; for SQLite), as Dumbo now supports multiple relational databases.&lt;/p&gt;
&lt;p&gt;Having that, you can do stuff like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/dumbo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;batchCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;CREATE TABLE test_users (id SERIAL PRIMARY KEY, name TEXT)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;INSERT INTO test_users (name) VALUES (&apos;Alice&apos;), (&apos;Bob&apos;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And also do queries:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;count&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;SELECT COUNT(*) as count
      FROM test_users
      WHERE &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userIds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’ll handle query parameterisation, data escaping, etc.&lt;/p&gt;
&lt;p&gt;It can also handle transactions:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; tx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;INSERT INTO test_users (name)
        VALUES (&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;firstUserName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;), (&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;secondUserName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;SELECT *
        FROM test_users
        WHERE &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userIds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As simple as this looks, you should already have the question about the first tradeoff I made in your head.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why on earth I think that’s a sane move to write my own multi-database driver?!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Well, I agree, that’s not the best move at the first glance, but let me explain to you why in &lt;strong&gt;MY&lt;/strong&gt; context this actually makes more sense:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Yes, there are tools like Knex, Kysely, Drizzle, etc. in Node.js land that handle similar stuff. They’re nice, I really like them, I really do, but… But they are all big and are bringing a lot of their conventions, and when I’m building storage tools like Emmett and Pongo, I need to have more control. I don’t want those tools and their limitations drive my architecture decisions. I also don’t want to be surprised when the creator decides to drop working on it, or when I become a victim of a supply chain attack. Still, when I want a new kitchen table, I don’t start by going to the forest with a saw. I’m still using existing database drivers, what’s more I’m allowing people to choose the one they prefer. I just don’t want to be driven (and also my users be driven) by some other higher abstraction tool.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Even with that, this decision can seem like a bold “how hard can it be?!” statement. And it is, but… But I’ve built, or was co-authoring, such tools in the past. Some were proprietary, some were Open Sourced (see &lt;a href=&quot;https://github.com/JasperFx/weasel&quot;&gt;Weasel&lt;/a&gt;). And they served me well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And last but not least, the last point. Well, I only had those two above. OK, I can add that I didn’t plan to make it a general-usage tool, just a small one for my needs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So I did.&lt;/p&gt;
&lt;p&gt;And here we’re at the moment when I had to cheat on transactions because of those decisions.&lt;/p&gt;
&lt;h2 id=&quot;cloudflare-d1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cloudflare-d1&quot; aria-label=&quot;cloudflare d1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cloudflare D1&lt;/h2&gt;
&lt;p&gt;All relational databases seem similar, but only until you start using them extensively, or until you need to write your own storage library. Then you learn stuff that not necessarily the stuff you’d like to spend time on. For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What’s the difference between databases like PostgreSQL and single-threaded databases like SQLite or DuckDB, and how concurrent processing can be surprising&lt;/li&gt;
&lt;li&gt;or that sqlite3 only calls the first query, but the next silently ignores,&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Still, well, abstractions like Dumbo give the possibility to &lt;em&gt;massage&lt;/em&gt; such cases behind the scenes.&lt;/p&gt;
&lt;p&gt;And then Cloud Databases and SaaS Databases came into play, like Cloudlfare D1.&lt;/p&gt;
&lt;p&gt;I was motivated by the generous sponsorship from &lt;a href=&quot;https://www.linkedin.com/in/samhatoum&quot;&gt;Sam Hatoum&lt;/a&gt; to make supporting Emmett and Pongo a top priority. Thanks Sam, appreciate that! And I made it, but I had to cheat a bit.&lt;/p&gt;
&lt;p&gt;Databases like Cloudflare D1 and Supabase expose databases as pay-as-you-go services. They optimise deployment, making it highly scalable, so you don’t need to care about it. It’s cost-effective for start-ups and scenarios when you’re not under a huge load. If you are, then you’ll need to pay more, but they also give you autoscaling at least.&lt;/p&gt;
&lt;p&gt;They do it by exposing the database API through HTTP API, for instance &lt;a href=&quot;https://docs.postgrest.org/en/v14/&quot;&gt;PostgREST&lt;/a&gt;. This gives them easier management around throughput, security, etc., as each call is made as an HTTP request through the exposed API.&lt;/p&gt;
&lt;p&gt;Yet, that kills some options like: ekhm, transactions. The challenge with transactions is that they don’t scale (that’s why &lt;a href=&quot;https://www.youtube.com/watch?v=b2F-DItXtZs&quot;&gt;MongoDB is WebScale&lt;/a&gt;). They don’t scale, as you’d need to open a transaction, do some freehand operations, and then commit or rollback. That means (typically) you need to keep a connection open during that time. If you’re building SaaS, it’s a no-go, because sneaky users would open it for a few hours, do crazy stuff, and kill your SaaS resources’ utilisation.&lt;/p&gt;
&lt;p&gt;But, boy, aren’t transactions one of the selling points of relational databases? They do, so how to proceed?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then you need to cheat, as I did.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;repeatable-reads-batches-etc&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#repeatable-reads-batches-etc&quot; aria-label=&quot;repeatable reads batches etc permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Repeatable reads, batches, etc.&lt;/h2&gt;
&lt;p&gt;Cloudflare D1 doesn’t provide transaction data for the reasons I outlined, but it doesn’t leave us without other tools. Those two tools to let me do the smoke and mirrors trick are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sessions,&lt;/li&gt;
&lt;li&gt;SQL batches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What are sessions? &lt;a href=&quot;https://developers.cloudflare.com/d1/best-practices/read-replication&quot;&gt;Per Cloudflare Docs&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A session encapsulates all the queries from one logical session for your application. For example, a session may correspond to all queries coming from a particular web browser session. All queries within a session read from a database instance which is as up-to-date as your query needs it to be. Sessions API ensures &lt;a href=&quot;https://developers.cloudflare.com/d1/best-practices/read-replication/#replica-lag-and-consistency-model&quot;&gt;sequential consistency&lt;/a&gt; for all queries in a session.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Essentially, that means that we’re getting &lt;a href=&quot;https://jepsen.io/consistency/models/repeatable-read&quot;&gt;repeatable reads&lt;/a&gt;. So when we’re starting specific sessions, they will be handled sequentially.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And now, the second ingredient: Batches. Per &lt;a href=&quot;https://developers.cloudflare.com/d1/worker-api/d1-database/#batch&quot;&gt;Cloudflare docs&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Sends multiple SQL statements inside a single call to the database. This can have a huge performance impact as it reduces latency from network round trips to D1. D1 operates in auto-commit. Our implementation guarantees that each statement in the list will execute and commit, sequentially, non-concurrently.&lt;/p&gt;
&lt;p&gt;Batched statements are &lt;a href=&quot;https://www.sqlite.org/lang_transaction.html&quot;&gt;SQL transactions&lt;/a&gt;. If a statement in the sequence fails, then an error is returned for that specific statement, and it aborts or rolls back the entire sequence.&lt;/p&gt;
&lt;p&gt;To send batch statements, provide D1Database::batch a list of prepared statements and get the results in the same order.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So Cloudflare didn’t allow us to do the full, freehand transaction, but they allowed us to send multiple statements that internally will be executed as a SQLite Transaction. When the request is handled, it’ll open a transaction, run the statements, and return the results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cool, let’s mix this soup together, as having that, I decided to:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fail automatically if someone tries to create a transaction on Cloudflare D1 with an error:&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;D1 does not support SQL transactions (BEGIN/COMMIT/ROLLBACK/SAVEPOINT).  Use { mode: “session_based” } to opt-in to session+batch semantics, or use ‘connection.execute.batchCommand() for atomic multi-statement execution.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then they get clear information on the first try.&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Allow users to explicitly open a session-based transaction by providing mode:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; tx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;INSERT INTO test_users (name)
      VALUES (&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;firstUserName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;), (&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;secondUserName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; tx&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;SELECT *
              FROM test_users
              WHERE &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; userIds&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; mode&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;session_based&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When they do it, they will need to be aware of the limitations of the tool they have. So that this will internally create a D1 session, and only handle a single batch of operations properly. We’re mimicking the sequential processing by the session-based repeatable reads capability. Still, we need to remember that we won’t be able to roll back changes across multiple statements. Only a single command or batch command is an atomic operation.&lt;/p&gt;
&lt;p&gt;We can’t, for instance, run a batch of updates, and fail the whole batch if one update didn’t change any record. The batch will only fail if the database throws an exception. An exception can be in SQLite only called by a table constraint or trigger.&lt;/p&gt;
&lt;p&gt;By making this choice to require explicit mode and naming it explicitly, I didn’t manage to cover all cases, but at least made it safe, so people need to learn about this non-default behaviour and be more careful about it. When designing an API, it’s usually better to start with a more strict option and do a bit of &lt;em&gt;“scarification”&lt;/em&gt; sometimes.&lt;/p&gt;
&lt;p&gt;Still, when I implemented that in &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt; and &lt;a href=&quot;https://github.com/event-driven-io/pongo&quot;&gt;Pongo&lt;/a&gt;, I intentionally used it to enable event appends, but document operations, etc.&lt;/p&gt;
&lt;p&gt;If you’d like to try it, you can check Emmett’s or Pongo’s beta versions.&lt;/p&gt;
&lt;p&gt;For Pongo, you can install it with:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @event-driven-io/pongo@0.17.0-beta.21&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And use it as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; d1PongoDriver &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/pongo/cloudflare&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  driver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; d1PongoDriver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  database&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  transactionOptions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; mode&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;session_based&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or in Emmett by installing:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @event-driven-io/emmett-sqlite@0.43.0-beta.1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And use it as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getSQLiteEventStore &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett-sqlite&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; d1EventStoreDriver &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett-sqlite/cloudflare&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; eventStore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getSQLiteEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  driver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; d1EventStoreDriver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  database&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Still, even if you don’t care about Emmett, Pongo, and my Open Source project, I hope that this will give you decent inspiration for your own tradeoffs analysis.&lt;/p&gt;
&lt;p&gt;I hope that you learned a bit about how design APIs work, how to check the guarantees of your tools and learn to walkaround them when you have to.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[On rebuilding read models, Dead-Letter Queues and Why Letting Go is Sometimes the Answer]]></title><description><![CDATA[In the last article, I explained how to rebuild Event-Driven Read Models in a safe and resilient way. I asked readers to let me know if they…]]></description><link>https://event-driven.io/en/rebuilding_read_models_skipping_events/</link><guid isPermaLink="false">https://event-driven.io/en/rebuilding_read_models_skipping_events/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/15181cbf6a5249da225a8603462fe780/a331c/2026-01-19-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC9ElEQVR42g3P2W/aBgCAcf9/01RpT33YpL20VVWlmfa0VN1eqjZbj7RdmmRRsyQlBIVchIQjQDDmsrlcgzF28AEYbOPbMbZLWpNpkb7X38MHfP86/uN65tdQ/uUZvJVrnNTxMkkxNCkMWUHpKTZPUBzVFymBrdLdVJPYLaKr6drzE+TBdha4s5T4eeNiIVx4n6zslrAs1h51mjIKS58rjjGaflPG4mggjEVT6CvjCs3UWT5P9tcu0EcBEPjhXfLeZvb3g+JKqraH4NU20T45zK98YBJxXxNvfN21LUVXLc+wXIcSRKzH7yPtZ5HSL0EIuPMuef9TYeEQfnteD8I4THOJV29OF57R+6dEMu27iqFbhq1OppNb3JWkIslsgujzk/L8LV4I5A4yMAiVUayZb1GYqEDRWPjpX8xRYojUZWHMD+Vrf6rZ5kjXOiOxNRBAgltONR7ePt/9+3wuAL3dz2ajMTx6SF2cp5aX449/Y5Y+dsOxBsoKqsbrsqArrKxQgpTALmNodztP/LSeAr5bSs5tgouR2r85IgY2crFU9J+tyE68GU5OC4jJS11FFQzD9Dx1MsF5KVjCq6z4ZL90fxsCHm4UVvageuCAgz8HU3gQZONVMZyhQaTfaQ0okqclra8Zij3pq0aW4NC+vFWk5kPl+RAMrJ0Sq5HG6jG8eIauXBCf0q1olQvFqmvh8qtw+WO6fVzjcF5hFCuCsscNNlRl32fai2fY3E4BGIhuiVA3st3dMlvqqpTstoSr9TT9Yq/yx0b6QwzdLtEFSmnyFkSN9yrsUb0frQ+OEC4A0oB5NROVKcYY5MAeqNOe4iVqwzfRztPN8p/B3A7MnrYEfGTLlk9JDkTJ9Z4JEjJCqbWOCtiO77gzx5up1jdG8lo9q0ioCKVnMBlCebAzLjC6YF1rzs3I+EqKE1KYoIzeZswaoQK+/99t7pcZN/ZqtIkyJiO6I+1aMr7yskdKDim7zpfZlXejXvlD7fqSnzRp/XJgVzvq/81NaQEJBi1TAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/15181cbf6a5249da225a8603462fe780/507e6/2026-01-19-cover.webp 200w,
/static/15181cbf6a5249da225a8603462fe780/28a80/2026-01-19-cover.webp 400w,
/static/15181cbf6a5249da225a8603462fe780/8d2ea/2026-01-19-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/15181cbf6a5249da225a8603462fe780/36ca5/2026-01-19-cover.png 200w,
/static/15181cbf6a5249da225a8603462fe780/a3397/2026-01-19-cover.png 400w,
/static/15181cbf6a5249da225a8603462fe780/a331c/2026-01-19-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/15181cbf6a5249da225a8603462fe780/a331c/2026-01-19-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In the last article, I explained &lt;a href=&quot;/en/rebuilding_event_driven_read_models/&quot;&gt;how to rebuild Event-Driven Read Models in a safe and resilient way&lt;/a&gt;. I asked readers to let me know if they find any blind spots in my design.&lt;/p&gt;
&lt;p&gt;Well, I found one myself.&lt;/p&gt;
&lt;p&gt;This article is about that edge case, but more importantly, it’s about the rabbit hole I went down thinking about how to “fix” it. At the end of this hole, there is a nice learning about dealing with distributed systems. Sometimes the best engineering isn’t about preventing all failures. It’s about recognising your blind spots and making sure they don’t catch you off guard.&lt;/p&gt;
&lt;h2 id=&quot;everyone-has-a-plan-until-they-get-punched-in-the-mouth&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#everyone-has-a-plan-until-they-get-punched-in-the-mouth&quot; aria-label=&quot;everyone has a plan until they get punched in the mouth permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Everyone has a plan until they get punched in the mouth&lt;/h2&gt;
&lt;p&gt;Let me recap the situation described in &lt;a href=&quot;/en/rebuilding_event_driven_read_models/&quot;&gt;the previous article&lt;/a&gt;. We’re using PostgreSQL to store events and read models. We want to rebuild an inline projection, one that’s applied in the same transaction as the appended event.&lt;/p&gt;
&lt;p&gt;In the design from my previous article, during a rebuild, we:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mark the projection as “rebuilding”.&lt;/li&gt;
&lt;li&gt;Skip inline projections (the rebuild process will catch up anyway).&lt;/li&gt;
&lt;li&gt;Process all historical events.&lt;/li&gt;
&lt;li&gt;Mark the projection as “active” again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The hybrid locking strategy with advisory locks and status checks ensures that inline projections know when to skip and that only one rebuild runs at a time.&lt;/p&gt;
&lt;p&gt;Sounds solid. Here’s where it breaks:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f8043733ebf202e179c6672d29b0caa9/7b314/2026-01-19-mermaid1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB70lEQVR42mVSyW4UMRCd//8WJASROJAI5UCQiEg4IMEJGGamV9vtfSmvuLtnC1ildtmq16/86m1KKYEndtBsp6POZVkp5z7YgxYH0D6l9bKAsqixpCtOLue8mUtdtsyZyWV/AcsUiNHUu1jPUWj2bMkn0dyK5s7gByeec2SbtTqE4ABSSkppYyyAr5dG27TQ5sgledTsiaNHQb4o+qTp5xzoEewhMMrHAbVNf9i3fTcKLjmXOR97WZdzEEI8Hy/ME6FowEOPxgHXv/TdwJk4geteW8jgXK2syRJlE2Hk/fupvW1+3XTbd1qi2rU11mgjxL/MAEfm9XaTk/Outaoxch/9WHJYmeqnvt9aJ4RijDM6GSOtUVWcKmjV8Kj22jY4KC+X1sbVV0Jwho5/3vS/X6Hda3J4S9ubGinKKzBcgZe2lFJSSEIoIQSN3Th0dELWyOCt96aqcAZHWEYVYkwpxzijOVcIc4Q4xgKhqQbGdBHspdreB845IQwjPDTf2v1XMnyn4w9QWzBbb3cp1acWa+E/cC7BRaehJimBxB9pfz92H9rmDg/3NZf4IcfZkmBVBFtNcwHP3m4k28mojzaOOY/J99Z0wfl88rbjZvyp0baY6crbPoMKXoUccjk5QMcgPMjo03nU0UfDa+R5WjPzX3VxJlPqKTZdAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/f8043733ebf202e179c6672d29b0caa9/507e6/2026-01-19-mermaid1.webp 200w,
/static/f8043733ebf202e179c6672d29b0caa9/28a80/2026-01-19-mermaid1.webp 400w,
/static/f8043733ebf202e179c6672d29b0caa9/8d2ea/2026-01-19-mermaid1.webp 800w,
/static/f8043733ebf202e179c6672d29b0caa9/68fc1/2026-01-19-mermaid1.webp 1200w,
/static/f8043733ebf202e179c6672d29b0caa9/43d96/2026-01-19-mermaid1.webp 1600w,
/static/f8043733ebf202e179c6672d29b0caa9/7ddb3/2026-01-19-mermaid1.webp 5362w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/f8043733ebf202e179c6672d29b0caa9/36ca5/2026-01-19-mermaid1.png 200w,
/static/f8043733ebf202e179c6672d29b0caa9/a3397/2026-01-19-mermaid1.png 400w,
/static/f8043733ebf202e179c6672d29b0caa9/a331c/2026-01-19-mermaid1.png 800w,
/static/f8043733ebf202e179c6672d29b0caa9/8537d/2026-01-19-mermaid1.png 1200w,
/static/f8043733ebf202e179c6672d29b0caa9/1a152/2026-01-19-mermaid1.png 1600w,
/static/f8043733ebf202e179c6672d29b0caa9/7b314/2026-01-19-mermaid1.png 5362w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/f8043733ebf202e179c6672d29b0caa9/a331c/2026-01-19-mermaid1.png&quot;
            alt=&quot;mermaid1&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The rebuild process checks for new events, sees none (because event 1000 is still in an uncommitted transaction), declares victory, and sets the projection to active. Meanwhile, the append transaction has already decided to skip the inline projection. When it finally commits, the event exists but was never projected. The rebuild process is done, and only new events will update the projection during the next update to the new event append.&lt;/p&gt;
&lt;p&gt;We could handwave it with &lt;a href=&quot;/en/no_it_can_never_happen/&quot;&gt;it won’t ever happen!&lt;/a&gt;. But, well, under load, this will happen eventually. You might not even notice it, but it will. The timing window is small, but with enough throughput, it becomes a certainty.&lt;/p&gt;
&lt;h2 id=&quot;the-rabbit-hole-of-fixes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-rabbit-hole-of-fixes&quot; aria-label=&quot;the rabbit hole of fixes permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Rabbit Hole of “Fixes”&lt;/h2&gt;
&lt;p&gt;My first instinct was to engineer my way out of this. Surely with enough clever coordination, we can close this gap?&lt;/p&gt;
&lt;p&gt;Let me walk you through the rabbit hole.&lt;/p&gt;
&lt;h3 id=&quot;attempt-1-wait-for-in-flight-transactions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#attempt-1-wait-for-in-flight-transactions&quot; aria-label=&quot;attempt 1 wait for in flight transactions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Attempt 1: Wait for In-Flight Transactions&lt;/h3&gt;
&lt;p&gt;PostgreSQL provides &lt;em&gt;pg_snapshot_xmin(pg_current_snapshot())&lt;/em&gt; which tells us the oldest transaction that’s still running. The idea: after processing events, wait until all potentially-skipping transactions have completed before marking the projection as active.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6e98d7a96879259a92a8c0d1d4c28642/804dc/2026-01-19-mermaid2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABdUlEQVR42l1SWW7dMAz0/W+T9iNAUBQ9RlHkoy0cP1uSZa3UQi2l7CwvHRg0RXEocqSp915tNwsEnlvuF1rrsuY52FvyoZb+Fh7GC7c+p/1PxzCNWO5wRCu8VR4gWOuc89wY7qzGjK31e2QIiqNXveLUzj0iEUcIubysy7KuN+atV4eupbbPoGSAeDmT3A/KsNY7B96PEvQZ4ziX83xT2pR34DC1Vmc94phlun4hRIred9d6oyQp1S4kWWqKMyFO/5DqjZx0TspZFkHmeNQaqKOrq0He1cXhfGcb52ynWrR8JTvxXa1PenvS7Jtljwg/34WNMZY74MBom6Yj7zw5x5y00SyAxGRpsJGTkZKMtlLIbROCjmViXTljnPGd5KBag1zSdrx8kfNXOT8Y/uNO1uYgZMRrVQfIDjcAtPPyz3t23S4Qee75Qy1R8l9nbgn+fyQgzPIr7b87wiDX1LIvCLWVD7WhFYPZ0RifH0nLAUHXYHot/wDmgnvVaGDC0wAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/6e98d7a96879259a92a8c0d1d4c28642/507e6/2026-01-19-mermaid2.webp 200w,
/static/6e98d7a96879259a92a8c0d1d4c28642/28a80/2026-01-19-mermaid2.webp 400w,
/static/6e98d7a96879259a92a8c0d1d4c28642/8d2ea/2026-01-19-mermaid2.webp 800w,
/static/6e98d7a96879259a92a8c0d1d4c28642/68fc1/2026-01-19-mermaid2.webp 1200w,
/static/6e98d7a96879259a92a8c0d1d4c28642/43d96/2026-01-19-mermaid2.webp 1600w,
/static/6e98d7a96879259a92a8c0d1d4c28642/c1d07/2026-01-19-mermaid2.webp 5872w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/6e98d7a96879259a92a8c0d1d4c28642/36ca5/2026-01-19-mermaid2.png 200w,
/static/6e98d7a96879259a92a8c0d1d4c28642/a3397/2026-01-19-mermaid2.png 400w,
/static/6e98d7a96879259a92a8c0d1d4c28642/a331c/2026-01-19-mermaid2.png 800w,
/static/6e98d7a96879259a92a8c0d1d4c28642/8537d/2026-01-19-mermaid2.png 1200w,
/static/6e98d7a96879259a92a8c0d1d4c28642/1a152/2026-01-19-mermaid2.png 1600w,
/static/6e98d7a96879259a92a8c0d1d4c28642/804dc/2026-01-19-mermaid2.png 5872w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/6e98d7a96879259a92a8c0d1d4c28642/a331c/2026-01-19-mermaid2.png&quot;
            alt=&quot;mermaid2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why it fails:&lt;/strong&gt; We don’t know what we’re waiting for. While we wait for existing transactions to complete, new transactions start and make their own skip decisions. The target keeps moving. We can never “catch up” because new skips happen while we’re waiting for old ones to become visible.&lt;/p&gt;
&lt;h3 id=&quot;attempt-2-use-transaction-ids-as-boundaries&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#attempt-2-use-transaction-ids-as-boundaries&quot; aria-label=&quot;attempt 2 use transaction ids as boundaries permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Attempt 2: Use Transaction IDs as Boundaries&lt;/h3&gt;
&lt;p&gt;PostgreSQL assigns monotonic transaction IDs to each transaction when it starts. This seems useful—what if we record a “sealing” transaction ID when we’re ready to complete the rebuild, and use it to make decisions?&lt;/p&gt;
&lt;p&gt;The logic would be:&lt;/p&gt;
&lt;p&gt;When rebuild is ready to complete, record sealing_txid = current_transaction_id&lt;/p&gt;
&lt;p&gt;Any event from a transaction with ID lower than the sealing point was “in flight” during rebuild, so it should be handled by async
Events from transactions with higher IDs started after sealing, so they can safely use inline projections (the read model will be ready)&lt;/p&gt;
&lt;p&gt;Sounds reasonable?&lt;/p&gt;
&lt;p&gt;Here’s why it doesn’t work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The core problem: transaction ID order ≠ commit order.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When a transaction starts, PostgreSQL assigns it the next available transaction ID. But transactions don’t commit in the order they started. A transaction that started earlier (lower ID) might take longer to complete and commit after a transaction that started later (higher ID).&lt;/p&gt;
&lt;p&gt;Let’s trace through a concrete scenario:
&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2943d3babe77b71ff4bc14af0afbb9be/186ec/2026-01-19-mermaid3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 86%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAIAAABSJhvpAAAACXBIWXMAAAsTAAALEwEAmpwYAAACGUlEQVR42o1TaWvcQAzN//9FPWhpaKBQQqFfS49tdrPrtefwXBrN6T5v0oQuIekgm/FYT3rS01wsWKXRGMzO5JB3wX5XUjE1M9jhRzIj/vfVafF92XLZpRL73cFygSfOX9T2ldy8dtNlSM5ELr0xOW9USQ+eS1sWyymU2u4PVnBvPecSnZ2ZA0cmT7VUIfThMFnrc8rrSrnk4p2vj9hT5jVqa9YSc/WepFSzNtMofv38LYRSUjvnQyAspeZa6yO4FWXFRztdHW/ei9u3Tn1zluHqfTDGxciR4il5gbmzzKiupiHHQ/S3PQ81WyJurXJM0yil1FobaxxzQk4QeJp2Svlu41yAKxgOB6D1OAohpBQK4RDlBTAzYwMnkMehFHq/PwJMIc6z+afmMzAwp80qgbNaywmw41GsJQg9TRKez4Bryr3y1o7v5AEtfOPNHvXSiUWAirW9mLmVvCofyRsD2SRakPi/a26tY0K0MjAQhmCQDZ0/B/dGmQX5sTcHMOQFSWsdRgUTIia0ijBbFAI+ay2nYV9tBWfamPGDGi6D/lpKJYqJM96YE9BZFW7o3+oeY3qaNmajlAYxgw+QGgzFJFGkMRTcTg5X+vhp3GEWP3t17eR1r/YErkuY4rx3lftAfqOVy6l6YaebEvQaunr038Xd1mxgnrYlbnuL9+Bsa9SpcFOZpxhwlTrNbMdK5oFk7ItsXbWe/t7nP8J/1XRx8cWVAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/2943d3babe77b71ff4bc14af0afbb9be/507e6/2026-01-19-mermaid3.webp 200w,
/static/2943d3babe77b71ff4bc14af0afbb9be/28a80/2026-01-19-mermaid3.webp 400w,
/static/2943d3babe77b71ff4bc14af0afbb9be/8d2ea/2026-01-19-mermaid3.webp 800w,
/static/2943d3babe77b71ff4bc14af0afbb9be/68fc1/2026-01-19-mermaid3.webp 1200w,
/static/2943d3babe77b71ff4bc14af0afbb9be/43d96/2026-01-19-mermaid3.webp 1600w,
/static/2943d3babe77b71ff4bc14af0afbb9be/a6462/2026-01-19-mermaid3.webp 6385w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/2943d3babe77b71ff4bc14af0afbb9be/36ca5/2026-01-19-mermaid3.png 200w,
/static/2943d3babe77b71ff4bc14af0afbb9be/a3397/2026-01-19-mermaid3.png 400w,
/static/2943d3babe77b71ff4bc14af0afbb9be/a331c/2026-01-19-mermaid3.png 800w,
/static/2943d3babe77b71ff4bc14af0afbb9be/8537d/2026-01-19-mermaid3.png 1200w,
/static/2943d3babe77b71ff4bc14af0afbb9be/1a152/2026-01-19-mermaid3.png 1600w,
/static/2943d3babe77b71ff4bc14af0afbb9be/186ec/2026-01-19-mermaid3.png 6385w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/2943d3babe77b71ff4bc14af0afbb9be/a331c/2026-01-19-mermaid3.png&quot;
            alt=&quot;mermaid3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Here’s what happened:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tx 99&lt;/strong&gt; started first and got the lowest transaction ID. It inserted an event and decided to skip the inline projection (status was ‘rebuilding’). But then it got slow—maybe network latency, maybe the application did other work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tx 100&lt;/strong&gt; (the rebuild) started second, recorded &lt;em&gt;sealing_txid = 100&lt;/em&gt;, and prepared to complete.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tx 101&lt;/strong&gt; started third. It checked: “Is my transaction ID (101) &gt;= sealing_txid (100)?” Yes, so it assumed the read model was ready and processed its inline projection. It committed successfully.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tx 100&lt;/strong&gt; marked the projection as active and committed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tx 99&lt;/strong&gt; finally committed. But it had already decided to skip the projection back when status was ‘rebuilding’. That decision was made, the skip happened, and the event is now missing from the read model.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The fundamental issue: &lt;strong&gt;we can’t see uncommitted transactions.&lt;/strong&gt; When Tx 100 set the sealing point, it had no way to know that Tx 99 was still out there, holding an event that would skip projection. Transaction 99 is invisible until it commits, but by then it’s too late.&lt;/p&gt;
&lt;p&gt;You might think: “Just wait until all transactions before the sealing point have committed!” PostgreSQL even provides &lt;em&gt;pg_snapshot_xmin(pg_current_snapshot())&lt;/em&gt; which tells you the oldest active transaction. But this leads us back to Attempt 1—while we wait, new transactions start and make their own skip decisions. The target keeps moving.&lt;/p&gt;
&lt;p&gt;I wrote about this exact problem in &lt;a href=&quot;/en/ordering_in_postgres_outbox/&quot;&gt;How Postgres sequences issues can impact your messaging guarantees&lt;/a&gt;. The same visibility challenges that affect outbox ordering apply here. Transaction IDs are useful for ordering within committed data, but they can’t help us coordinate with transactions that haven’t committed yet.&lt;/p&gt;
&lt;h3 id=&quot;attempt-3-lock-appends-during-transition&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#attempt-3-lock-appends-during-transition&quot; aria-label=&quot;attempt 3 lock appends during transition permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Attempt 3: Lock Appends During Transition&lt;/h3&gt;
&lt;p&gt;What if we use advisory locks more aggressively? The idea: when rebuild is ready to complete, acquire an exclusive lock that blocks the entire append path. While holding this lock, flip the status to ‘active’. No appends can be in progress during the flip, so no race condition, right?&lt;/p&gt;
&lt;p&gt;Here’s the proposed flow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rebuild finishes processing historical events&lt;/li&gt;
&lt;li&gt;Acquire exclusive advisory lock (all new appends must wait)&lt;/li&gt;
&lt;li&gt;Set status = ‘active’&lt;/li&gt;
&lt;li&gt;Release lock&lt;/li&gt;
&lt;li&gt;Waiting appends resume and sees ‘active’, processes inline normally&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It should work. In theory. We’re creating a synchronisation point where nothing can slip through. But there’s a subtle problem: &lt;strong&gt;the skip decision was already made inside the append transaction.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An append transaction doesn’t just check the lock and status at one instant. It does several things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;BEGIN transaction&lt;/li&gt;
&lt;li&gt;INSERT the event&lt;/li&gt;
&lt;li&gt;Try to acquire a shared advisory lock&lt;/li&gt;
&lt;li&gt;Check projection status&lt;/li&gt;
&lt;li&gt;Decide: process inline or skip?&lt;/li&gt;
&lt;li&gt;Execute that decision&lt;/li&gt;
&lt;li&gt;COMMIT&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The decision in step 5 happens &lt;em&gt;inside&lt;/em&gt; the transaction. If the transaction saw ‘rebuilding’ status, it was skipped. That decision is now part of the transaction’s pending work. The transaction might be waiting to commit or doing other work, but the skip decision has already been made.&lt;/p&gt;
&lt;p&gt;Our exclusive lock in the completion flow blocks step 3 - new transactions can’t acquire the shared lock. But what about transactions that already passed step 5 and are just waiting to commit?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9bd7348156952524b739722203592523/f3dd3/2026-01-19-mermaid4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 90.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAIAAADUsmlHAAAACXBIWXMAAAsTAAALEwEAmpwYAAACS0lEQVR42oWTaWvcMBCG9///jpLQD4UGQhvYJk2h0KahRyik6bWXd2Wtbdm6JevsrJMNmyYhQphh5Eea9x1plHOOJrGlZivFkTHEQyal3AW3VKLstYkhb0YyTcHKP7qe5RSHTB5BVtZnzXRM5sdkfkJXJ8k3sNCnSI2R3gXYadgvOW14F4zI27GBo8PRIy1mvVn4vuCs8T7mlLUyMca8M4yxu5nRbaS0xeuuKKrFAhUFwrhqmi6EO7C1j8DGOspMVfOqIkDSjnEun4aDmTlxIdsvLT6rVx84I13HhZCMiadhqxa8/U7WFwR/q9FXzjoptXNeKf2f5vtwUuySk8+0Pq/Rx2r5ntOSC2WNhbIfMyztlL1w6pfhV079jv3fnPT2nF5KxShnjArBlBJay5Sg7XGYGziG4KGLWhsPLYoxRX8NQwaUa90TfI6nL9HksCle8+qYV2PdfdrAKTCGjyg+alevuvKIloe9vLqrMIUe97ro9VLQiRFz8AjinPxGcx4qsVbH4CBO29sHCr1PIVi6fkvQmw6fEHTMqlPRnPLqNHo22nGyv7Xn+kYqqcGz9bou0XqNK9APXQBZD1wSgOFXmEJoIYwxPQRwNmiW0kAeChma9xAM9nBokXas+YEmL8rpAZ4eiGrM67Ek7wZpj1/PYQH0O1EdkmK/XT6v5/v1fK+e7VXTZ0793Pxz/2HAe+ZIdSvBkDYtKLfgXOd1IdvScu0NvDvY1LaI44klixtLruHkkqXOMgdfJ8ONihQ771kIbnuUV7TnjZPkFv4Hz+sJvEJWBXIAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/9bd7348156952524b739722203592523/507e6/2026-01-19-mermaid4.webp 200w,
/static/9bd7348156952524b739722203592523/28a80/2026-01-19-mermaid4.webp 400w,
/static/9bd7348156952524b739722203592523/8d2ea/2026-01-19-mermaid4.webp 800w,
/static/9bd7348156952524b739722203592523/68fc1/2026-01-19-mermaid4.webp 1200w,
/static/9bd7348156952524b739722203592523/43d96/2026-01-19-mermaid4.webp 1600w,
/static/9bd7348156952524b739722203592523/6e7a4/2026-01-19-mermaid4.webp 5695w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/9bd7348156952524b739722203592523/36ca5/2026-01-19-mermaid4.png 200w,
/static/9bd7348156952524b739722203592523/a3397/2026-01-19-mermaid4.png 400w,
/static/9bd7348156952524b739722203592523/a331c/2026-01-19-mermaid4.png 800w,
/static/9bd7348156952524b739722203592523/8537d/2026-01-19-mermaid4.png 1200w,
/static/9bd7348156952524b739722203592523/1a152/2026-01-19-mermaid4.png 1600w,
/static/9bd7348156952524b739722203592523/f3dd3/2026-01-19-mermaid4.png 5695w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/9bd7348156952524b739722203592523/a331c/2026-01-19-mermaid4.png&quot;
            alt=&quot;mermaid4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The timing here is tricky. Advisory locks in PostgreSQL can be either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transaction-scoped&lt;/strong&gt; (&lt;em&gt;pg_advisory_xact_lock&lt;/em&gt;): released automatically when transaction commits&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Session-scoped&lt;/strong&gt; (&lt;em&gt;pg_advisory_lock&lt;/em&gt;): held until explicitly released or session ends&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we use transaction-scoped locks for inline projections (which makes sense—you want the lock tied to the transaction lifetime), then the append transaction might have already released its shared lock by the time we try to acquire an exclusive lock. The lock protected the status check, but the transaction is still running with its skip decision already made.&lt;/p&gt;
&lt;p&gt;Even if we could perfectly synchronise the lock acquisition, there’s another problem: &lt;strong&gt;advisory locks are session-scoped, not durable.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If the connection dies while holding the exclusive lock:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The lock releases immediately (that’s how advisory locks work)&lt;/li&gt;
&lt;li&gt;We might have partially updated the status&lt;/li&gt;
&lt;li&gt;The system is now in an unknown state&lt;/li&gt;
&lt;li&gt;Other transactions resume with potentially inconsistent data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6fe525cb478c19689e763e530ba1eddd/0a09a/2026-01-19-mermaid5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAABt0lEQVR42m2S247TMBRF+/+/wKC+8sIfMJRBA0ICjYRAQlOpTQudxPE1ceJL7GMbp0lpNMxRnMtJlr33jldRJlkpVduo01QQY+VNqTvsrQmQOzGPodHkoMgxaXZ+HmsVdFLMWOGCnVshRQYDVj3WvXYOvPcAXjVGlLapo23Ps40TrPIw2oKHdKkYxxdt22JC65pg3OaD0k5KY4xNixphay0ALNgRllIp7TnvyNO3p8M9Qw+Sfu/FD68fB/XL622M4QybJRzAHsAWqnk0cpvvnfjMyltWbcjpFv95J9AdrzaS3McIz+HgED/e1Ps1LtZot8b7V97+noMEeEn2DI9qvS356S0pbkixpsVrcnxj+/1kJweXTV9CiVfPzuVEc64uxGS6A6/ei/pTg++c2k3ktLL9f2VjBiE0qjiqMK5pjSjjOVsVQlh++gLs9U/JvpbHDTo9CN4SwihhgomGt1PsS/i5Z1l9ocVHWnwwzfbyp6KJQWjde3elgwPb2b5JYbjCsU8SGU3ccnuW5+2JnFbgZ8eG9/Wuq4vYk3RRtApDcMp77YOfW/mSmR5cPsM/5TAEI8HI6K/K/wIv0uuZLziZegAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/6fe525cb478c19689e763e530ba1eddd/507e6/2026-01-19-mermaid5.webp 200w,
/static/6fe525cb478c19689e763e530ba1eddd/28a80/2026-01-19-mermaid5.webp 400w,
/static/6fe525cb478c19689e763e530ba1eddd/8d2ea/2026-01-19-mermaid5.webp 800w,
/static/6fe525cb478c19689e763e530ba1eddd/68fc1/2026-01-19-mermaid5.webp 1200w,
/static/6fe525cb478c19689e763e530ba1eddd/43d96/2026-01-19-mermaid5.webp 1600w,
/static/6fe525cb478c19689e763e530ba1eddd/c3c9d/2026-01-19-mermaid5.webp 5167w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/6fe525cb478c19689e763e530ba1eddd/36ca5/2026-01-19-mermaid5.png 200w,
/static/6fe525cb478c19689e763e530ba1eddd/a3397/2026-01-19-mermaid5.png 400w,
/static/6fe525cb478c19689e763e530ba1eddd/a331c/2026-01-19-mermaid5.png 800w,
/static/6fe525cb478c19689e763e530ba1eddd/8537d/2026-01-19-mermaid5.png 1200w,
/static/6fe525cb478c19689e763e530ba1eddd/1a152/2026-01-19-mermaid5.png 1600w,
/static/6fe525cb478c19689e763e530ba1eddd/0a09a/2026-01-19-mermaid5.png 5167w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/6fe525cb478c19689e763e530ba1eddd/a331c/2026-01-19-mermaid5.png&quot;
            alt=&quot;mermaid5&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We can’t build reliable coordination on something that vanishes when connections fail. This is exactly why the original article combined advisory locks with persistent status checks—but that combination doesn’t solve this particular race condition.&lt;/p&gt;
&lt;h3 id=&quot;tldr-on-attempts&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr-on-attempts&quot; aria-label=&quot;tldr on attempts permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR on Attempts&lt;/h3&gt;
&lt;p&gt;Every “fix” follows the same pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Identify a coordination point.&lt;/li&gt;
&lt;li&gt;Discover there’s a window before that point we can’t see.&lt;/li&gt;
&lt;li&gt;Try to close that window.&lt;/li&gt;
&lt;li&gt;Create a new window somewhere else.&lt;/li&gt;
&lt;li&gt;Repeat.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We’re not solving the problem. We’re relocating it.&lt;/p&gt;
&lt;p&gt;So is it a bug in the initial design? Depending how you look on that. I see it as an example of fundamental property of concurrent systems. PostgreSQL’s isolation guarantees mean uncommitted transactions are invisible to other transactions. That’s a feature, not a bug - but it means there’s always a window we can’t see into.&lt;/p&gt;
&lt;h2 id=&quot;stop-fighting-start-tracking&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#stop-fighting-start-tracking&quot; aria-label=&quot;stop fighting start tracking permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Stop Fighting, Start Tracking&lt;/h2&gt;
&lt;p&gt;After chasing my tail through various “solutions,” I stepped back and asked a different question.&lt;/p&gt;
&lt;p&gt;Instead of: &lt;em&gt;“How do we prevent events from being skipped?”&lt;/em&gt; (which requires blocking appends or seeing into uncommitted transactions—both unacceptable because of performance, guarantees etc.)&lt;/p&gt;
&lt;p&gt;I started to think on: &lt;em&gt;“How do we know when an event was skipped?”&lt;/em&gt; and &lt;em&gt;“How do we ensure skipped events get processed eventually?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Both of these are solvable.&lt;/p&gt;
&lt;p&gt;If we can’t prevent skips from happening, let’s make them visible. When an inline projection skips, it could record that it skipped in the same transaction as the event append.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d783b0d5b838843e333dee2672ae3f53/91948/2026-01-19-mermaid6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAABuUlEQVR42oVTWY7bMAzN/S/RTpGeYk7Rv6KZdsaIosWSbG3WvpTupG0aYKYE8SDIfKRJPh16H82MopoXqanRfB9gfXSw/TRcK7wm4u2cAs2hXa93O0Bc1SPwrJCt66hbeyX/hmFbYS2dncbR4ez/Jf+ykos1rpYKBiVjjASzlDOc4aaUuq46l/qXd0uGCC6k2zyl8zSdMabThBAi5zOeZ7E5f0EUYsZbZK1NCEHKFaKlXDiXi1wFl5wvMcRlWf9DXldFCCNkBqSES7FAogsiSmkhlvfIUDPnAikY4+CUzICQxTkPzVPKa21vkpUyGDN8oVAZOmeUE0wpYQhhRmchJAxuXJfw6jdkmKe1Dmbu3KZWzZgwJmw+WxcBhdDv/TaQY0ohwJqS1vaCkBJfzfIN3K4nhr4E+5z9lPwUt5ea2Q25VAN7rm1fdC2tjVY0fznipwd8+oROD+j0Ef84ku/H+fkzfvpg5serwrobRe/y7Ga0+EcI8AGarL6ltXsetSxO1q2PDJoafW/h0FtvesSlKGyrGtXey1PXwltBxtAYLjmWO3m23GvoLe3Y7yU4QMy2ZngegNt14FfyTxfCJu1Y7lSeAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d783b0d5b838843e333dee2672ae3f53/507e6/2026-01-19-mermaid6.webp 200w,
/static/d783b0d5b838843e333dee2672ae3f53/28a80/2026-01-19-mermaid6.webp 400w,
/static/d783b0d5b838843e333dee2672ae3f53/8d2ea/2026-01-19-mermaid6.webp 800w,
/static/d783b0d5b838843e333dee2672ae3f53/68fc1/2026-01-19-mermaid6.webp 1200w,
/static/d783b0d5b838843e333dee2672ae3f53/43d96/2026-01-19-mermaid6.webp 1600w,
/static/d783b0d5b838843e333dee2672ae3f53/63d68/2026-01-19-mermaid6.webp 3812w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d783b0d5b838843e333dee2672ae3f53/36ca5/2026-01-19-mermaid6.png 200w,
/static/d783b0d5b838843e333dee2672ae3f53/a3397/2026-01-19-mermaid6.png 400w,
/static/d783b0d5b838843e333dee2672ae3f53/a331c/2026-01-19-mermaid6.png 800w,
/static/d783b0d5b838843e333dee2672ae3f53/8537d/2026-01-19-mermaid6.png 1200w,
/static/d783b0d5b838843e333dee2672ae3f53/1a152/2026-01-19-mermaid6.png 1600w,
/static/d783b0d5b838843e333dee2672ae3f53/91948/2026-01-19-mermaid6.png 3812w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d783b0d5b838843e333dee2672ae3f53/a331c/2026-01-19-mermaid6.png&quot;
            alt=&quot;mermaid6&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;If the append transaction rolls back, the skip record also rolls back (there’s no event to worry about). If it commits, we have a durable record of exactly what was skipped.&lt;/p&gt;
&lt;p&gt;In Emmett, I’m using a dedicated &lt;em&gt;emt_system_messages&lt;/em&gt; table rather than reusing the regular &lt;em&gt;emt_messages&lt;/em&gt; table or creating a simple “skipped events” table. This might seem like over-engineering—why not just create a simple table with projection ID and event position? Or why not just reuse the existing messages table?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why a dedicated system messages table?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The regular &lt;em&gt;emt_messages&lt;/em&gt; table is for business events—the actual domain events that drive your application. Mixing system-level concerns (like “this projection skipped this event during rebuild”) with business events pollutes the event log and makes it harder to reason about.
A simple “skipped_events” table with just (projection_id, event_position) could be enough, but in the long term, it may be hard to maintain. As systems evolve, we’ll have more cases where we might want to skip events. Plus, having system events recorded with all metadata gives us full observability of the internals of our system. We could even make tables for projections and processors’ status, built as read models from system events! Still, let’s hold our horses and get back to our use case.&lt;/p&gt;
&lt;p&gt;The system messages table could mirror the structure of regular messages with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Global position sequencing (with all the transaction visibility handling from the &lt;a href=&quot;/en/ordering_in_postgres_outbox/&quot;&gt;outbox ordering article&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Transaction ID tracking for proper visibility checks&lt;/li&gt;
&lt;li&gt;Archiving support via &lt;em&gt;is_archived&lt;/em&gt; flag&lt;/li&gt;
&lt;li&gt;Partitioning for performance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we created a throwaway “skipped events” table, we’d need to solve all these problems again. We’d essentially be building a second event log with the same guarantees.&lt;/p&gt;
&lt;p&gt;The skip could be stored as a system message where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stream ID&lt;/strong&gt; = the projection identifier (name + version), so we can query all skips for a specific projection&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Message data&lt;/strong&gt; = reference to the original event (sequence ID from the event log)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Message metadata&lt;/strong&gt; = processor ID, reason for skip, timestamp&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Message type&lt;/strong&gt; = indicates this is a “skip during rebuild” system event&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Won’t this table grow too much? It may, but we can archive skip events when they’re no longer needed.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When the rebuild processor handles a skipped event, we could archive it. In Emmett, this means setting is_archived = true on the record. The table is partitioned by this flag. Archived records automatically move to a separate partition, which can even be on a different disk drive.&lt;/p&gt;
&lt;p&gt;Why archive instead of delete?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Audit trail: You can see what was skipped and when it was processed&lt;/li&gt;
&lt;li&gt;Debugging: If something goes wrong, you have history to investigate&lt;/li&gt;
&lt;li&gt;Idempotency: If a processor crashes and restarts, it won’t reprocess already - archived skips&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Later, we could define retention policies and clean up old archived records. We could do it more aggressively than for business events, since these are operational records, not business history. You might keep business events forever, but archived skip records only need to stick around long enough for debugging (days or weeks, not years).&lt;/p&gt;
&lt;p&gt;The same processor that handles regular events for the projection could also process its skipped records. When it reads an event from the main event log and applies the projection, it also checks for and archives any corresponding skip record for that event position. This keeps everything in sync.&lt;/p&gt;
&lt;p&gt;As mentioned, this also opens the door for other system events in the future - not just rebuild skips, but potentially failed projections, poison messages, or audit records. The infrastructure would already be in place, separated from the business event log.&lt;/p&gt;
&lt;h2 id=&quot;the-final-flow&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-final-flow&quot; aria-label=&quot;the final flow permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Final Flow&lt;/h2&gt;
&lt;p&gt;Let me walk through how this works in practice, bringing together all the pieces.&lt;/p&gt;
&lt;h3 id=&quot;during-rebuild&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#during-rebuild&quot; aria-label=&quot;during rebuild permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;During rebuild&lt;/h3&gt;
&lt;p&gt;The projection status is ‘rebuilding’. The async rebuild processor works through historical events from the beginning. Meanwhile, normal operations continue. Events are appended, and inline projections check the status. When they see ‘rebuilding’, they skip the projection but record a skip message in emt_system_messages within the same transaction.&lt;/p&gt;
&lt;h3 id=&quot;when-rebuild-catches-up&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#when-rebuild-catches-up&quot; aria-label=&quot;when rebuild catches up permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;When rebuild catches up&lt;/h3&gt;
&lt;p&gt;The rebuild processor eventually reaches the current position where no new events are visible. At this point, it sets the status to ‘active’. From now on, new appends will process their inline projections normally.&lt;/p&gt;
&lt;p&gt;But what about those skip records? The events they reference exist, but their projections were never applied.&lt;/p&gt;
&lt;h3 id=&quot;draining-the-skipped-events&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#draining-the-skipped-events&quot; aria-label=&quot;draining the skipped events permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Draining the skipped events&lt;/h3&gt;
&lt;p&gt;The rebuild processor (or a dedicated processor, or a manual trigger—your choice) now queries emt_system_messages for skip records belonging to this projection. Using the same transaction visibility rules from the outbox pattern (transaction_id &amp;#x3C; pg_snapshot_xmin), it only sees skip records from committed transactions.&lt;/p&gt;
&lt;p&gt;For each skip record:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Find the referenced event in the event log (or use its data if stored in skipped event)&lt;/li&gt;
&lt;li&gt;Apply the projection for that event&lt;/li&gt;
&lt;li&gt;Archive the skip record (set is_archived = true)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9b10c8241901d1b66ba9c29d4a150f02/b229a/2026-01-19-mermaid7.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 90.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAIAAADUsmlHAAAACXBIWXMAAAsTAAALEwEAmpwYAAACGUlEQVR42nVTyW4UMRCd//8OruTEJQe4RuGCBBI3YJKZ3rzb3e194XUnCiB6SlapbNdzvXq2T622OjfP4zJZz2LWFdb+s1IrzYEk39uF5ECTR9KpllZMW4gjT2KebJKllgNwrpXlIHK4aClLJNGjxmk7NZYW2qpscrmldss4/XQ+v5vGO3gtH7ByettbltV7H1MKIXofXrzfohh2W/hXQx4W9tmKx2x/voJfmtR67rqx76ZhmLrrMAwE08tzP0207yesdz2nfGVcv9X7A5ZC7dU8IYxzyZgUQgkhMVVKjyNdlzXGoKQCYh9/gRkT62pzzsbMShmjDZBaGzCCn+clphhinM1yUHnL0LOznjM5DoRMDH4aKSWs70ZGRfDB6JkzcQy21qEyZQIkKeXgIqUG+XEkYJ5SRoKSRz2DJOcCnAGASOfzBeNy6Z6fr4wLgLGFBEhwVFkZ3BauxDmHnsETzLdBOKbebbe3rit0PQAjA9o451Hkeukp4UCCAjrvruO8rNhCz4zyI7A0eBEplxhTKRXyQjkIhgAJcTdcJAr8Ay7umxo/dD/uhl/vnX58y4M8GDhxX0j7YkDn/4Dz+l1196r/KK/3UX+59bZLa6lWg6dSSnkF1+1XBZ5M7zxNWR3+yO1LTsmP0T0ZNWVPokPaDtZtGTw7q7m3Sdz8kkNyQ7RnI7cg2B0MwZCfcUaD34Ibhs1Yctp93on/BjpkDVEp3NY3AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/9b10c8241901d1b66ba9c29d4a150f02/507e6/2026-01-19-mermaid7.webp 200w,
/static/9b10c8241901d1b66ba9c29d4a150f02/28a80/2026-01-19-mermaid7.webp 400w,
/static/9b10c8241901d1b66ba9c29d4a150f02/8d2ea/2026-01-19-mermaid7.webp 800w,
/static/9b10c8241901d1b66ba9c29d4a150f02/68fc1/2026-01-19-mermaid7.webp 1200w,
/static/9b10c8241901d1b66ba9c29d4a150f02/43d96/2026-01-19-mermaid7.webp 1600w,
/static/9b10c8241901d1b66ba9c29d4a150f02/9feae/2026-01-19-mermaid7.webp 3535w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/9b10c8241901d1b66ba9c29d4a150f02/36ca5/2026-01-19-mermaid7.png 200w,
/static/9b10c8241901d1b66ba9c29d4a150f02/a3397/2026-01-19-mermaid7.png 400w,
/static/9b10c8241901d1b66ba9c29d4a150f02/a331c/2026-01-19-mermaid7.png 800w,
/static/9b10c8241901d1b66ba9c29d4a150f02/8537d/2026-01-19-mermaid7.png 1200w,
/static/9b10c8241901d1b66ba9c29d4a150f02/1a152/2026-01-19-mermaid7.png 1600w,
/static/9b10c8241901d1b66ba9c29d4a150f02/b229a/2026-01-19-mermaid7.png 3535w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/9b10c8241901d1b66ba9c29d4a150f02/a331c/2026-01-19-mermaid7.png&quot;
            alt=&quot;mermaid7&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The skip record and the event are committed in the same transaction. This gives us a simple invariant: if an event exists, either its projection was applied inline (status was ‘active’) or a skip record exists (status was ‘rebuilding’). There’s no third option where an event exists without a trace.&lt;/p&gt;
&lt;p&gt;The drain process might find new skip records appearing. Transactions that were in flight during the status change, committed after we started draining. That’s fine. We keep querying until no more visible skip records exist. They will stop appearing as we alread stopped rebuilding processor, so no more inline projections should be skipped.&lt;/p&gt;
&lt;p&gt;The “drain skipped events” phase can be automatically triggered by finishing projection rebuild. It could be handled as the 2nd phase of rebuild processor, or triggering a dedicated one. It could be also just handled by a human initiating the drain.&lt;/p&gt;
&lt;p&gt;This flexibility lets user choose the approach based on the specific operational requirements.&lt;/p&gt;
&lt;h2 id=&quot;the-dead-letter-queue-pattern&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-dead-letter-queue-pattern&quot; aria-label=&quot;the dead letter queue pattern permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Dead Letter Queue Pattern&lt;/h2&gt;
&lt;p&gt;What we’ve built is essentially a &lt;strong&gt;Dead Letter Queue (DLQ)&lt;/strong&gt;—a place where messages that couldn’t be processed normally are stored for later handling.&lt;/p&gt;
&lt;p&gt;This pattern exists in every serious messaging system:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Apache Kafka:&lt;/strong&gt; Dead Letter Topics for messages that fail consumer processing&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RabbitMQ:&lt;/strong&gt; Dead letter exchanges for rejected or expired messages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS SQS:&lt;/strong&gt; Redrive policies that move messages to a DLQ after N failures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Azure Service Bus:&lt;/strong&gt; Built-in dead-letter sub-queues for each queue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pattern is universal because all messaging systems face the same fundamental problem: sometimes messages can’t be processed immediately, and you need a place to store them without blocking the rest of the system.&lt;/p&gt;
&lt;p&gt;It’s a topic in its own right, as it’s not perfect and should be used cautiously. Many teams fail to apply them correctly. The DLQ becomes like a car alarm in a parking lot, technically signalling a problem, practically ignored by everyone. And that’s what I see in many systems: teams set up DLQs and do nothing about them.&lt;/p&gt;
&lt;p&gt;It starts innocently. You configure the DLQ “just in case.” A few messages end up there during a deployment. Someone says, “We’ll look at it later.”&lt;/p&gt;
&lt;p&gt;More messages accumulate. The DLQ becomes background noise—a number on a dashboard that nobody checks. Eventually, something critical lands there, and nobody notices until a customer complains.&lt;/p&gt;
&lt;p&gt;That’s why in the discussed design, skip records aren’t meant to accumulate indefinitely. The rebuild processor drains them during completion.&lt;/p&gt;
&lt;p&gt;Retention policies clean up archived records after a reasonable period. If skip records exist for too long, that’s a signal that something is wrong with the rebuild process - and users should know about it.&lt;/p&gt;
&lt;p&gt;A DLQ is only helpful if it’s monitored, processed, and understood why messages end up there. Otherwise, it’s just a fancy way to lose data slowly rather than immediately.&lt;/p&gt;
&lt;h2 id=&quot;the-broader-lesson&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-broader-lesson&quot; aria-label=&quot;the broader lesson permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Broader Lesson&lt;/h2&gt;
&lt;p&gt;This article isn’t really about PostgreSQL advisory locks or projection rebuilding. It’s about how we approach problems in distributed systems.&lt;/p&gt;
&lt;p&gt;When we find a race condition, the instinct is to fix it. Add a lock. Add a check. Add a coordination phase. But each “fix” often just moves the race condition somewhere else. We went through three attempts—waiting for transactions, using transaction ID boundaries, locking appends—and each one failed for a different reason. We weren’t solving the problem; we were relocating it.&lt;/p&gt;
&lt;p&gt;At some point, I had to ask myself: am I making this system more reliable, or just more complicated?&lt;/p&gt;
&lt;p&gt;The answer came when I changed the question. Instead of asking “how do I prevent events from being skipped?” I asked, “How do I know when an event was skipped, and how do I make sure it gets processed eventually?”&lt;/p&gt;
&lt;p&gt;The first question has no good answer, not without blocking appends, which defeats the purpose. The second question is straightforward: record the skip in the same transaction as the event, and process it later.&lt;/p&gt;
&lt;p&gt;A system isn’t trustworthy because it never fails. That’s impossible for anything sufficiently complex. A system is trustworthy because you know when it can fail, how it will fail, and how to recover. The skip tracking approach doesn’t prevent failures during the transition period. It makes them visible and recoverable. That’s a stronger guarantee than complex coordination machinery with hidden edge cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sometimes the best engineering decision is to accept what you can’t control and focus on what you can.&lt;/strong&gt; We can’t control PostgreSQL’s transaction visibility rules. We can’t see into uncommitted transactions. We can’t make the append decision and the rebuild completion atomic without stopping the world.&lt;/p&gt;
&lt;p&gt;What we can control is recording skipped items, ensuring those skips are processed, and making the whole thing observable. That’s enough.&lt;/p&gt;
&lt;p&gt;The blind spot I found wasn’t really about the specific race condition. It was a reminder that distributed systems have fundamental constraints we can’t engineer around, at least not without trade-offs worse than the original problem.&lt;/p&gt;
&lt;p&gt;Transaction isolation and concurrent systems mean we can’t have zero-downtime rebuilds with perfect inline projection consistency and no coordination overhead, all at the same time.&lt;/p&gt;
&lt;p&gt;Something has to give.&lt;/p&gt;
&lt;p&gt;What we get instead is explicit tracking of what was skipped, guaranteed eventual processing via the system messages table, observability into the transition period, and crash recovery that doesn’t lose data. The read model might be briefly inconsistent during the transition, but we know exactly what’s missing, and we have a clear path to fix it.&lt;/p&gt;
&lt;p&gt;That’s the kind of system I can reason about and trust.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;If you’re dealing with similar challenges, I’m happy to help through &lt;a href=&quot;mailto:oskar@event-driven.io&quot;&gt;consulting or mentoring&lt;/a&gt;.&lt;/strong&gt; You can also join the discussion in our &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Emmett Discord&lt;/a&gt;—we have a nice community working through these exact problems.&lt;/p&gt;
&lt;p&gt;Or check also other related resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/rebuilding_event_driven_read_models/&quot;&gt;Rebuilding Event-Driven Read Models in a safe and resilient way&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/ordering_in_postgres_outbox/&quot;&gt;How Postgres sequences issues can impact your messaging guarantees&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/projections_and_read_models_in_event_driven_architecture/&quot;&gt;Guide to Projections and Read Models in Event-Driven Architecture&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/distributed-locking-a-practical-guide/&quot;&gt;Distributed Locking: A Practical Guide&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/consumers_processors_in_emmett/&quot;&gt;Consumers, projectors, reactors and all that messaging jazz in Emmett&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_scale_projections_in_the_event_driven_systems/&quot;&gt;How to scale projections in the event-driven systems?&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/checkpointing_message_processing/&quot;&gt;Checkpointing the message processing&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/lets_talk_about_positions_in_event_stores/&quot;&gt;Let’s talk about positions in event stores&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Rebuilding Event-Driven Read Models in a safe and resilient way]]></title><description><![CDATA[Let’s make a soup today: a blog soup. We’ll mix multiple ingredients like: events (obviously), read models, inline and async projections…]]></description><link>https://event-driven.io/en/rebuilding_event_driven_read_models/</link><guid isPermaLink="false">https://event-driven.io/en/rebuilding_event_driven_read_models/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9e38ed3c86bf4b6ef2a88b6e89966001/a331c/2026-01-05-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAACnklEQVR42k1T224TMRDN/7+CKgQvhUrlhYsAiQdQq9LQVG2qkKRpmusm8V689q73ms0mRegw47SCh5HXXs+Zc+aMGyavEKcldFIgKSokvM8qpMUGgYpRVFuKHUy2RlrWyNZbcI69l67tmtMdQzk6LtAoqgc63CDNawpaLegGbqBw8OIVXh+9xeHRMd5//Ix3Hz7h28mZvRcRWBjl8JSG4y7gSh+GzhpXNz20rjo4+dFC725i2XkyxnDioDeY4PvpOZqtNjq9e3T7FLf3uBtObUFbOFSYigVCHSNKSjROzi7w/OAlDt8cY7rwSHqJkKgrUxILlr5FTImS2AR6H832CNd9geEixe08wsAxGIuCGJNknayJbgxfpZRYQxOIBSPgmL5ZGrN+Ct7PvQL3DDbVGK9y9MbKgitTocEXTLZBkqYwcYw0NYhMsgejYgzOjLXZF2DQYl2h3u6we/iNTb1DSfu8oP8sOS23BJZBLodYTXoQ4y7CYAk3jsjNtXW42uys62lR0yTUmC0DTBwPQZTZdkQZKTNrIkKAynD1HDIQkP4S3spBSM55OoFQHtozH19uBFyV750lttcDH+cdgeYvgfa9xvWdRH+e2P42+EJIycqdE8MBlqMuNAEJZSCkh9PeAM++9jEREiIMsZTStiEhs8r6D8qqxmLSgnBuqEUEqOISKs7gu0v4YgZ/NYPSIVwaA09LDFcazaGkfWaN4imwK6nqdAe4uLyCVAomSe2ZdVnHhno4gjPu2x56rkMSjR0VbnSe7YeY96woeFwv212c/SRAGhdLjM4bowVJ8UPqT2mfFj+riJxMyAQOQ02P6cw8BptgHv/xM8z42dE3G2Kf3iqgGZT7KecE7g/L4vGwifk+zH+zyGPGhczTv/wf4F+pqcGrdWclewAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/9e38ed3c86bf4b6ef2a88b6e89966001/507e6/2026-01-05-cover.webp 200w,
/static/9e38ed3c86bf4b6ef2a88b6e89966001/28a80/2026-01-05-cover.webp 400w,
/static/9e38ed3c86bf4b6ef2a88b6e89966001/8d2ea/2026-01-05-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/9e38ed3c86bf4b6ef2a88b6e89966001/36ca5/2026-01-05-cover.png 200w,
/static/9e38ed3c86bf4b6ef2a88b6e89966001/a3397/2026-01-05-cover.png 400w,
/static/9e38ed3c86bf4b6ef2a88b6e89966001/a331c/2026-01-05-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/9e38ed3c86bf4b6ef2a88b6e89966001/a331c/2026-01-05-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Let’s make a soup today: a blog soup. We’ll mix multiple ingredients like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;events (obviously),&lt;/li&gt;
&lt;li&gt;read models,&lt;/li&gt;
&lt;li&gt;inline and async projections,&lt;/li&gt;
&lt;li&gt;rebuilding read models,&lt;/li&gt;
&lt;li&gt;backfilling new ones with data from existing events,&lt;/li&gt;
&lt;li&gt;scaling async processing horizontally,&lt;/li&gt;
&lt;li&gt;distributed locking,&lt;/li&gt;
&lt;li&gt;PostgreSQL and its Advisory Locks.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sounds a lot? Well, the soup should be nutritious.&lt;/p&gt;
&lt;p&gt;In an event-driven way, after handling business logic, we record new facts and call them events. They gather information about what has happened. That brings many benefits, such as business observability by keeping a log of them. Especially if we’re doing Event Sourcing, we can make the next decision based on them.&lt;/p&gt;
&lt;p&gt;Typically, we’re using events in two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reacting to them, triggering and integrating the steps of our business workflow,&lt;/li&gt;
&lt;li&gt;projecting them, and getting the flattened interpretation of our system state inside read models.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Such processing can happen asynchronously, but doesn’t have to. If we’re using a transactional database (like PostgreSQL, SQLite, or even MongoDB), we can update our read models in the same atomic transaction that stores new events. Such a process is typically called &lt;em&gt;inline projection&lt;/em&gt;. Event stores like &lt;a href=&quot;/en/emmett_projections_testing/&quot;&gt;Emmett&lt;/a&gt; and &lt;a href=&quot;/en/projections_in_marten_explained/&quot;&gt;Marten&lt;/a&gt; allows that. Still, you can do the same if you’re using &lt;a href=&quot;/en/outbox_inbox_patterns_and_delivery_guarantees_explained/&quot;&gt;outbox pattern&lt;/a&gt;, then you can achieve the same without Event Sourcing.&lt;/p&gt;
&lt;p&gt;Inline processing is tempting because we’re getting immediate consistency. Yet, there’s no free lunch here. We’re slowing down our event append as we need to process more, and our transactions will be open longer, which can cause deadlocks, etc. We also can’t take advantage of batching event processing.&lt;/p&gt;
&lt;p&gt;Also, as &lt;a href=&quot;https://www.linkedin.com/in/bastian-waidelich-84865221&quot;&gt;Bastian Waidelich&lt;/a&gt; rightfully pointed out to me, using inline projections also increases the coupling and fragility of our business logic. If the inline projection fails (due to a bug in its projection logic, a database constraint or other random issue), then we won’t be able to append our event, which is counterintuitive, as why would read model block our business logic (e.g. confirming shopping cart).&lt;/p&gt;
&lt;p&gt;My thumb rule is that for single stream, simple projections, I prefer inline projections, but for more complex or workflow processing, I’d go with async.&lt;/p&gt;
&lt;p&gt;The big benefit of a durable event log is that we can correct past mistakes and gain more insights from existing data.&lt;/p&gt;
&lt;p&gt;How does it look in practice? Let’s say we initially had a basic read model that showed a summary of the specific shopping cart. In TypeScript this could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartSummary&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  _id&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Besides the data, we also need a method that represents how we apply events on top of the existing state to get the next, evolved state.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; evolve &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProductItemAdded &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ProductItemRemoved&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  document &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemRemoved&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;removing&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; withAdjustedTotals &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;removing&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; by &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; plusOrMinus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; by &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalAmount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
      productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unitPrice &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; plusOrMinus&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItemsCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; plusOrMinus&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Sounds fine, but well, it may appear that either we, implementing it, or our business, through requirements, forgot about some requirements. Or we didn’t forget anything, but just requirements evolved as they tend to always do.&lt;/p&gt;
&lt;p&gt;What if we need to handle now also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Show the shopping cart status and show whether the shopping cart is open, confirmed or cancelled.&lt;/li&gt;
&lt;li&gt;not only show totals, but also a list of product items with their details.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our new read model would look like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartSummary&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  _id&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Opened&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Confirmed&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Cancelled&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  productItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProductItem&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  unitPrice&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And here we should ask ourselves the following questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is it really the same read model or another one that’ll be used in another place in our UI? Maybe the initial just shows basic data in the menu bar, and this will be used as the summary before confirmation?&lt;/li&gt;
&lt;li&gt;If the read model is the same, then are you fine with downtime where you clean the old data, and reprocess events?&lt;/li&gt;
&lt;li&gt;If it’s the new read model, do we need to backfill it with the old data?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s no best practice here; we need to do the drill and be prepared for multiple options.&lt;/p&gt;
&lt;h2 id=&quot;add-new-vs-update-existing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#add-new-vs-update-existing&quot; aria-label=&quot;add new vs update existing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Add new vs update existing.&lt;/h2&gt;
&lt;p&gt;In our case, my initial guess would be that this should be a new read model. We need to add significantly more data and new event handling for confirmation and cancellation.&lt;/p&gt;
&lt;p&gt;You could say that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Why add a new read model with similar data? Can’t we just do a subquery?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Of course, you can. I wrote about that more in &lt;a href=&quot;/en/how_to_create_projections_of_events_for_nested_object_structures/&quot;&gt;How to create projections of events for nested object structures?&lt;/a&gt;. This can make sense if those read models will always evolve together.&lt;/p&gt;
&lt;p&gt;If you need to run multiple &lt;em&gt;views&lt;/em&gt; on the same read models, you’re increasing coupling. As such, when you’re rebuilding the read model, then potential downtime will impact both. Also, each time you adjust one view, ensure you haven’t broken the others.&lt;/p&gt;
&lt;p&gt;You’re getting a smaller storage size, and potentially don’t need to remember multiple read models, but are doing it just one.&lt;/p&gt;
&lt;p&gt;My experience shows that optimising for the end storage until we check that it’s too big isn’t a good driver. Nowadays, storage is cheap, so my default is to keep read models separated, and also not reuse the same evolve logic. A bit of code duplication won’t harm us, but we’ll see benefits as our models evolve. We’ll decrease the cognitive load.&lt;/p&gt;
&lt;p&gt;Still, if those models are indeed the same, or we bet they’ll constantly evolve together, then it could be fine to reuse them.&lt;/p&gt;
&lt;h2 id=&quot;in-place-update-bluegreen-rebuild-and-backfilling-data&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#in-place-update-bluegreen-rebuild-and-backfilling-data&quot; aria-label=&quot;in place update bluegreen rebuild and backfilling data permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;In place update, blue/green rebuild and backfilling data&lt;/h2&gt;
&lt;p&gt;Ok, I was already using &lt;em&gt;rebuilding&lt;/em&gt; word multiple times. But how do we actually do it?&lt;/p&gt;
&lt;p&gt;If you’ve read articles on &lt;a href=&quot;/en/checkpointing_message_processing/&quot;&gt;checkpointing&lt;/a&gt; or &lt;a href=&quot;/en/lets_talk_about_positions_in_event_stores/&quot;&gt;positions in event stores&lt;/a&gt;,  you already know that each event in the event store/outbox can have its unique, monotonic position. We can subscribe to notifications about new events and process them one by one. That’s how &lt;a href=&quot;/en/consumers_processors_in_emmett/&quot;&gt;consumers and processors&lt;/a&gt; work in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;. Once we process a specific event, we can store the checkpoint in the end storage. This enables resilient failover when our processor dies for some reason. When we restart, we’ll read the last processed checkpoint first and start listening for events from that point.&lt;/p&gt;
&lt;p&gt;We could reuse this not only for failover but also for rebuilds. Instead of starting processing from the last known checkpoint, we could reset the checkpoint in our database to a specific point (e.g., the beginning of the log). Then we’ll get recorded events again.&lt;/p&gt;
&lt;p&gt;And this is the moment we need to decide whether to do an in-place update or a blue/green rebuild.&lt;/p&gt;
&lt;p&gt;In-place means that we’re the same storage target. We need to truncate it and then apply data from scratch, typically. The downside is that when we clear it, our read models won’t be up to date with the current state of our event store. We already have newer events recorded, but our read models are empty. We need to fill them in.&lt;/p&gt;
&lt;p&gt;The same happens when we create a new read model based on the existing events. When we create a new read model, it won’t magically contain data from existing events.&lt;/p&gt;
&lt;p&gt;Typically, we need to spin up a background worker (e.g., &lt;a href=&quot;/en/consumers_processors_in_emmett/&quot;&gt;Emmett consumer with a projector plugged in&lt;/a&gt;) that pulls all events since the beginning and runs projection logic. Obviously, that means we need to run it asynchronously, and depending on the data size, we’ll need to wait until it catches up. During that time, querying for the data will return outdated information - eventual consistency in practice.&lt;/p&gt;
&lt;p&gt;That’s why there’s another option. Instead of truncating existing data, we can keep it as it is, or even keep updating it in the old form. Having that, we can build a new read model in parallel. This read model could have a different name, or just a suffix, e.g. V2, V2.0.1, whatever we find helpful.&lt;/p&gt;
&lt;p&gt;Then, once this new read model catches up (so it has processed all events, or is close enough to the latest event with a defined threshold), we can switch queries from the old to the new read model storage.&lt;/p&gt;
&lt;p&gt;This is actually the preferred way, but it’s a bit more challenging when it comes to dynamically switching the query target. If you’re using Pongo, that’s not that hard, since you just switch the text-based collection name, which is just another table. But if you’re using an ORM, adding a new table dynamically and mapping it can be much more challenging.&lt;/p&gt;
&lt;h2 id=&quot;concurrency-issues-while-rebuilding-read-models&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#concurrency-issues-while-rebuilding-read-models&quot; aria-label=&quot;concurrency issues while rebuilding read models permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Concurrency issues while (re)building read models&lt;/h2&gt;
&lt;p&gt;To make it harder, eventual consistency is not the only challenge we’ll face. We also need to deal with concurrency and parallel processing.&lt;/p&gt;
&lt;p&gt;What should we do when a new event is appended while we’re rebuilding/backfilling the inline projection? If we try to update the end storage in the middle of the processing, we can end up with an inconsistent or erroneous state.&lt;/p&gt;
&lt;p&gt;Another issue is what to do if we’re in a Kubernetes-like setup and don’t have full control over the number of instances of the same service? Or what happens if we accidentally spin up multiple rebuild workers?&lt;/p&gt;
&lt;p&gt;Then we’re doomed, or at least the consistency of our read model data.&lt;/p&gt;
&lt;p&gt;How do we solve it? Let me explain my plan for &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;distributed-locking-and-postgresql-advisory-locks&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#distributed-locking-and-postgresql-advisory-locks&quot; aria-label=&quot;distributed locking and postgresql advisory locks permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Distributed Locking and PostgreSQL advisory locks&lt;/h2&gt;
&lt;p&gt;I encourage you to check my other article: &lt;a href=&quot;/en/distributed-locking-a-practical-guide/&quot;&gt;Distributed Locking: A Practical Guide&lt;/a&gt;. Yet, don’t worry, I won’t leave you with Read-The-Fucking-Manual type of answer.&lt;/p&gt;
&lt;p&gt;Distributed locks are a fundamental tool for coordinating concurrency across systems. We can use a central place, typically scalable on its own, that’ll be used in multiple instances of our service, to ensure that exactly one can request a lock and run specific code.&lt;/p&gt;
&lt;p&gt;In the mentioned article, I described multiple options and popular tools for handling that (e.g., Redis, Zookeeper, Kubernetes Replica Sets), as well as PostgreSQL and its Advisory Locks. Let me focus today on the last one and describe how I want to use it in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;’s PostgreSQL projection handling.&lt;/p&gt;
&lt;p&gt;PostgreSQL gives us two options for coordination.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Row-level locks&lt;/strong&gt; lock individual table rows. You do &lt;em&gt;SELECT … FOR UPDATE&lt;/em&gt;, and anyone else trying to modify that row waits. The lock is tied to the specific row in a table.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; some_table &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@key&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- make changes&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;COMMIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Such locks are straightforward, as we explicitly state what we want to lock. We could define the following table for projections:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;emt_projections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      version                       &lt;span class=&quot;token constant&quot;&gt;INT&lt;/span&gt;                    &lt;span class=&quot;token constant&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
      &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;                          &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token constant&quot;&gt;VARCHAR&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;             &lt;span class=&quot;token constant&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      name                          &lt;span class=&quot;token constant&quot;&gt;TEXT&lt;/span&gt;                   &lt;span class=&quot;token constant&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status                        &lt;span class=&quot;token constant&quot;&gt;TEXT&lt;/span&gt;                   &lt;span class=&quot;token constant&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
      &lt;span class=&quot;token constant&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; version&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The background worker could lock the projection by a specific name and version and set the status to “rebuilding”. Then, during handling the inline projection, we could use the same lock and check whether the status is “active”; if not, skip processing. Using a lock-in inline projection would also prevent the rebuilding process from starting, as they wouldn’t acquire the lock.&lt;/p&gt;
&lt;p&gt;And that’s a nuke option, as it would work but could create a performance problem. If every inline projection needs to grab a row lock on a coordination row, they will all be processed sequentially, one at a time. That’s a throughput killer when you’re appending thousands of events per second. Each of these append would require access to the lock for the specific projection type (e.g. our shopping cart summary), making not only updates but also event appends sequential. That’s not acceptable for most cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS&quot;&gt;Advisory locks&lt;/a&gt;&lt;/strong&gt; are different. They’re locks on arbitrary integers. PostgreSQL doesn’t care what the integer means—it just manages who holds the lock. No table rows involved. It also allows two modes: exclusive or shared.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Shared lock: many can hold simultaneously&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pg_try_advisory_xact_lock_shared&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Exclusive lock: only one holder, blocks shared locks&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pg_advisory_lock&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Those locks are either scoped to an open connection session or an opened transaction (with &lt;em&gt;xact&lt;/em&gt; with name) and released automatically once they end.&lt;/p&gt;
&lt;p&gt;Shared locks allow multiple sessions to access the lock with the same value. Exclusive lock blocks both other exclusive locks and new shared locks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This allows a design where shared locks are used for readers&lt;/strong&gt;, and exclusive locks for writers and maps directly to our problem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Inline projections take &lt;strong&gt;shared&lt;/strong&gt; locks as they run concurrently, and just need to check if there’s no async job updating these projections&lt;/li&gt;
&lt;li&gt;Rebuilds take &lt;strong&gt;exclusive&lt;/strong&gt; locks - they block inlines and other instances of async processing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We just need to do one more thing: since advisory locks take integers, we need to map our projection name and version to them. We can do it by a consistent hash algorithm, either in the application code or in PostgreSQL.&lt;/p&gt;
&lt;p&gt;PostgreSQL provides a built-in MD5 hash function. It’s not perfect, as it’s not a sophisticated hash, but it’s fast enough and predictable. In our case, we won’t have thousands of projections in our application, so the risk of a &lt;a href=&quot;https://en.wikipedia.org/wiki/Hash_collision&quot;&gt;hash collision&lt;/a&gt; is negligible. If you’re still worried it’s too high, we could store id in our projections table and use it instead of hash-mapping. Still, if we used md5 function, it could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// shared for inline projections&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pg_try_advisory_xact_lock_shared&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;x&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; substr&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;md5&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;?&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;bigint&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; acquired&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// exclusive for inline projections&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pg_try_advisory_xact_lock&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;x&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; substr&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;md5&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;?&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;bigint&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; acquired&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Where as query param, we’d pass the joined projection name and its version.&lt;/p&gt;
&lt;p&gt;Thanks to that, multiple inline projections can access the lock if it’s not held exclusively by the async (re)building worker. Thanks to that, we’re not blocking event appends because of the lock on the inline projections.&lt;/p&gt;
&lt;p&gt;An exclusive lock can be held only when there’s no single inline projection being applied at the moment.&lt;/p&gt;
&lt;p&gt;What’s also cool is that advisory locks can be used with two strategies: fail fast when the lock is held, or wait for the lock to be released. The first option would be useful for inline projections, and the second for rebuilds.&lt;/p&gt;
&lt;p&gt;Is that all? Well, not quite.&lt;/p&gt;
&lt;h2 id=&quot;why-advisory-locks-alone-arent-enough&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-advisory-locks-alone-arent-enough&quot; aria-label=&quot;why advisory locks alone arent enough permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why advisory locks alone aren’t enough&lt;/h2&gt;
&lt;p&gt;Advisory locks have a gap: they’re session-scoped. If the connection holding the lock dies, the lock releases automatically. For most cases, that’s fine and expected, but think through this scenario:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We have 1000 events in our event store.&lt;/li&gt;
&lt;li&gt;Rebuild starts, acquires exclusive lock.&lt;/li&gt;
&lt;li&gt;It truncates the projection and processes events 1-500.&lt;/li&gt;
&lt;li&gt;Connection dies (network blip, out of memory kill, whatever).&lt;/li&gt;
&lt;li&gt;Lock releases automatically.&lt;/li&gt;
&lt;li&gt;Projection contains data only for events 1-500.&lt;/li&gt;
&lt;li&gt;Inlines see no lock, start processing.&lt;/li&gt;
&lt;li&gt;Inline applies event 1001, ending up with a potentially corrupted state (as there may be some events between 500 and 1001 that would impact the state).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Advisory locks can’t persist across connection failures. We need to add something that does.&lt;/p&gt;
&lt;h2 id=&quot;the-hybrid-locking-approach&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-hybrid-locking-approach&quot; aria-label=&quot;the hybrid locking approach permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The hybrid-locking approach&lt;/h2&gt;
&lt;p&gt;Emmett already maintains tables for tracking processors and projections. The relevant ones:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; emt_projections&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    version         &lt;span class=&quot;token keyword&quot;&gt;INT&lt;/span&gt;         &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
    &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt;            &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    name            &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;        &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt;       &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;        &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;emt:default&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    kind            &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;        &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;          &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;        &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    definition      JSONB       &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{}&apos;&lt;/span&gt;::jsonb&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; version&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; LIST &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; emt_processors&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    last_processed_transaction_id XID8    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    version                       &lt;span class=&quot;token keyword&quot;&gt;INT&lt;/span&gt;     &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    processor_id                  &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt;                     &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;emt:default&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;                        &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;stopped&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    last_processed_checkpoint     &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;    
    processor_instance_id         &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;    &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;emt:unknown&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processor_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; version&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; LIST &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;em&gt;status&lt;/em&gt; column in the projections table can do what advisory locks can’t: persist status between connection crashes. Even if the connection dies, &lt;em&gt;status = ‘rebuilding’&lt;/em&gt; stays in the table. Inlines could check this and skip processing.&lt;/p&gt;
&lt;p&gt;The processor’s table tracks checkpoint progress. When a rebuild runs, it updates &lt;em&gt;last_processed_checkpoint&lt;/em&gt; as it goes. If it crashes and restarts, it can resume from where it left off rather than starting over.&lt;/p&gt;
&lt;p&gt;Potentially, we could reuse the processor’s table, but having a dedicated projection table could also be useful for diagnostics (e.g., storing the projection definition) and for switching queries when a new projection version catches up. We could select the highest active projection version.&lt;/p&gt;
&lt;p&gt;Having that, the query checking if the inline projection should be processed or skipped could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; lock_check &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pg_try_advisory_xact_lock_shared&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;x&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; substr&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;md5&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;$&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;bigint&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; acquired
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
status_check &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;active&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; is_active
    &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; emt_projections
    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; $&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; $&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; $&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; acquired &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; lock_check&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; acquired&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; is_active &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; status_check&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; is_active&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Two checks, both must pass:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Can we get a shared advisory lock? Fails if a rebuild holds exclusive.&lt;/li&gt;
&lt;li&gt;Is the projection status ‘active’? Fails if it’s ‘rebuilding’.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then for async (re)building worker:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; lock_check &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pg_try_advisory_lock&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;x&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; substr&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;md5&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;$&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;bit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;bigint&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; acquired
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
ownership_check &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; emt_processors &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        processor_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        version&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        processor_instance_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        last_processed_checkpoint&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        last_processed_transaction_id
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;$&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; $&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; $&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; $&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;running&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;0&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;0&apos;&lt;/span&gt;::xid8&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; CONFLICT &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;processor_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;partition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; version&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DO&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; processor_instance_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; $&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;running&apos;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; emt_processors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processor_instance_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; $&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- We already own it&lt;/span&gt;
       &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; emt_processors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processor_instance_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;emt:unknown&apos;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;-- Unclaimed&lt;/span&gt;
       &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; emt_processors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;stopped&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;-- Previous instance finished or crashed&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;RETURNING&lt;/span&gt; last_processed_checkpoint
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; acquired &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; lock_check&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; acquired&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; last_processed_checkpoint &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; ownership_check&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;checkpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Together Advisory locks prevent the race at the transition point where the connection was closed, and the rebuilding job is restarting. The status check handles crash recovery&lt;/p&gt;
&lt;p&gt;The rebuild acquires the exclusive lock &lt;em&gt;before&lt;/em&gt; updating the status. It waits for in-flight inlines (which hold shared locks) to finish.&lt;/p&gt;
&lt;p&gt;If the checkpoint is null, the UPDATE matched no rows, then another instance owns the processor and is actively running. Back off.&lt;/p&gt;
&lt;p&gt;If both succeed, you own the lock and the processor. The checkpoint tells you where to resume from (with a fallback to the beginning). Only then does it flip the status and start async processing.&lt;/p&gt;
&lt;p&gt;If the rebuild crashes, the lock is released, but the status remains ‘rebuilding’. Inlines check status and skip.&lt;/p&gt;
&lt;h2 id=&quot;the-scenarios-walkthrough&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-scenarios-walkthrough&quot; aria-label=&quot;the scenarios walkthrough permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The scenarios walkthrough&lt;/h2&gt;
&lt;p&gt;As a single good image speaks more than thousands of words, let me give you some diagrams to summarise how it works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. During inline projection while event appends:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each inline projection grabs a shared lock before applying,&lt;/li&gt;
&lt;li&gt;Multiple inlines can run concurrently (shared locks are compatible),&lt;/li&gt;
&lt;li&gt;Projection updates happen normally.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/dca429485d4697b503af71b7f8576b11/80631/2026-01-05-mermaid1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 87.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAACQ0lEQVR42o2UCXIiMQxFc/8bpmpgWLrp1b3YvXn9kURSEEiGMajssvGT9IX1BhpxSQg94MeEtXUII637JPsyknxlmOhRhhV12JAvRuaLXzB6J+dvAjR02QDOBOS7CmEiBwR3YyAQfSKdJ6YmKG/RRYsxWPytK5lrv0K57QZMG13uAjblMFcroqYIh4hoIx7HFiNKxxFanCeNYptxIZuCvwG/RggBSnVY1w3bZnE8ZXh/3yHLChwOZ+x3J6i2h7UOy7KiLGsEcpBwk+RH4DwvYlqTRnWLplFoaoWWZjPNWOhsMhOKoiIV0rcMnoB9N8A5D++DRJKdC1wuFUXToKpa2WfjDKqqQYwvgKrrMQwaZVGjoJQYxFH2/SCAguDsYByNOIgxvgAqAvbjFUiW56XMnDbDBEh7/LuctH2ZMuvEgp9OuRSDLzOci8Hr/NM0RdiRPCH8I0IOny9tVGWu9EQF0HqiAswCGLUWZ3zGOl+B4Xcgiz0MI81ePLNux0OGmrRiR6wdO2EIF+y/NGzbTi6xzdNCeurPSpcw5hot7xuKnKX4XUPaDy6gKluYcRJjKKe13x0l0itwEhk6+icczzk8RRhS+v7HjivBBn5uCUtrYfkZNh5u9vSGo0TBqUXKQDuLnJ/bOuOgB3rHGwp6iub+6d03h+xPKV0m6gSv41O3UdRV+uSgk8e+Kq/NgbpPe98cGODaCKsC5pIqqGjdBITlGchNILMzcrvgRO3rQvNxnTBQF7ppyBfitU09rn8arNmjfWn4AUbgKK2/RfxnAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/dca429485d4697b503af71b7f8576b11/507e6/2026-01-05-mermaid1.webp 200w,
/static/dca429485d4697b503af71b7f8576b11/28a80/2026-01-05-mermaid1.webp 400w,
/static/dca429485d4697b503af71b7f8576b11/8d2ea/2026-01-05-mermaid1.webp 800w,
/static/dca429485d4697b503af71b7f8576b11/68fc1/2026-01-05-mermaid1.webp 1200w,
/static/dca429485d4697b503af71b7f8576b11/43d96/2026-01-05-mermaid1.webp 1600w,
/static/dca429485d4697b503af71b7f8576b11/f7c5d/2026-01-05-mermaid1.webp 2815w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/dca429485d4697b503af71b7f8576b11/36ca5/2026-01-05-mermaid1.png 200w,
/static/dca429485d4697b503af71b7f8576b11/a3397/2026-01-05-mermaid1.png 400w,
/static/dca429485d4697b503af71b7f8576b11/a331c/2026-01-05-mermaid1.png 800w,
/static/dca429485d4697b503af71b7f8576b11/8537d/2026-01-05-mermaid1.png 1200w,
/static/dca429485d4697b503af71b7f8576b11/1a152/2026-01-05-mermaid1.png 1600w,
/static/dca429485d4697b503af71b7f8576b11/80631/2026-01-05-mermaid1.png 2815w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/dca429485d4697b503af71b7f8576b11/a331c/2026-01-05-mermaid1.png&quot;
            alt=&quot;mermaid1&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. When a rebuild starts:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rebuild grabs an exclusive lock (waits for any in-flight inlines to finish),&lt;/li&gt;
&lt;li&gt;Marks projection as “rebuilding”,&lt;/li&gt;
&lt;li&gt;New inlines see the lock or status and skip,&lt;/li&gt;
&lt;li&gt;Rebuild processes all historical events,&lt;/li&gt;
&lt;li&gt;Marks the projection as “active” and releases the lock,&lt;/li&gt;
&lt;li&gt;Inlines resume.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/94a56b07c4abbaaf770433de25df83e6/19f2d/2026-01-05-mermaid2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB50lEQVR42o2UCXLcIBBFff9LJo5j7RpJrEIs37/RTMqTyK5QRQGt5tGrXooGwhzhx4C0ACUVfB6pFHRxR+st2uAxxYCqkSOKauDnNxzLbxQzVv2XTKAjbGsN4pK/BL4bhSE4TMdegSUdKLqF6l/hpjcUO51A+ZpihjUOeGbVUQgU8aYthumGpu0x9CO6dkDfDViXBek48Lj8Uq3PGc45XA0BypAHw37Us7UORzj3StGzGP/o/z+QkHXdMI0zLRvPSUsnWh1j+heoteHljJQSjLF8WddVzuLOuiosy1ZlhlZpmbyzUX7q/AVcbmtVkovjQCv6CSOtUZuu8oGyzxcfw/v92sIbgQKb56W6IdBxmqtMXN0IvgI656+BMyGeHyVWHePTNszmMNXz7kPdC1B0JQmPRHxpoWFZ5CQxZC3mUpORuYqy7CVWYuU8LbVk5MEaQ8ougWL6d1mWy+KFxFWyO0iMGRaJ/SVQXPsOaAjc+ai7h2Qcb9hZl8b65zos5BwrS2X0kDZk5TyNTOCNfdtqhV8b3dXMupbSUXBzg7V7xaF6YN/uvczV9tLLFnFm7OJz/0UCm+jRWIX+cBji2cuQXlbvUN0P/gt+sq+7u4UHg78TFM71qp9dinVazpAf8aI+Hygyo8xQgR8qPJMZ4bPfdQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/94a56b07c4abbaaf770433de25df83e6/507e6/2026-01-05-mermaid2.webp 200w,
/static/94a56b07c4abbaaf770433de25df83e6/28a80/2026-01-05-mermaid2.webp 400w,
/static/94a56b07c4abbaaf770433de25df83e6/8d2ea/2026-01-05-mermaid2.webp 800w,
/static/94a56b07c4abbaaf770433de25df83e6/68fc1/2026-01-05-mermaid2.webp 1200w,
/static/94a56b07c4abbaaf770433de25df83e6/43d96/2026-01-05-mermaid2.webp 1600w,
/static/94a56b07c4abbaaf770433de25df83e6/e6ad6/2026-01-05-mermaid2.webp 4370w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/94a56b07c4abbaaf770433de25df83e6/36ca5/2026-01-05-mermaid2.png 200w,
/static/94a56b07c4abbaaf770433de25df83e6/a3397/2026-01-05-mermaid2.png 400w,
/static/94a56b07c4abbaaf770433de25df83e6/a331c/2026-01-05-mermaid2.png 800w,
/static/94a56b07c4abbaaf770433de25df83e6/8537d/2026-01-05-mermaid2.png 1200w,
/static/94a56b07c4abbaaf770433de25df83e6/1a152/2026-01-05-mermaid2.png 1600w,
/static/94a56b07c4abbaaf770433de25df83e6/19f2d/2026-01-05-mermaid2.png 4370w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/94a56b07c4abbaaf770433de25df83e6/a331c/2026-01-05-mermaid2.png&quot;
            alt=&quot;mermaid2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. If rebuild crashes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lock releases automatically&lt;/li&gt;
&lt;li&gt;Status stays “rebuilding”&lt;/li&gt;
&lt;li&gt;Inlines keep skipping until another rebuild completes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2a542e63e9b9755604542de17e69f2c7/0bcb8/2026-01-05-mermaid3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 98%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC3klEQVR42pVUyW4UMRTM//8EghAWCSlw48aJRWJX4ECEmCQz6X1zb3a7bRdl90wWskhYemOrxy7Xq/dcezCAbQBdGqhihikdnHSY4ZDMCqmWiKcBiZHozQw/rOrh2hiq2kA353CCYZf/9hxn2wJdLlGeC8yVgx0sNG9KZ4lM9jhva+RGoeEFYUwd3JijjFboig1cH8EZvQD6HzcBSkwYG8nbPQV+4yQZQs8oB4mRaxPodSg2r5D/fsh4xNhH8usB2ux12LGH7ZimCVLyFpNCtl+h2iPo7ghj/Q1d+QW6/0FiPzFU7yGiZ9zzGVX6Hl31kXs+QYrvgckVQAWlLOx4hOxkH+XmOfKzp0hPDpCsHiNdHfD7E6yPH5HNm3Cm6wbMs8HVcY3hMIwYxxFVWaEsSh7oeInE0I9XjlAMZ8Oq73uYUCi36HQdUKPIK1RVg9PTCGdnEaI4R5IUaNt+gXKXsQAOBLyDodYadS2oo0JRELhswgGlFua3jXsBfcpCtGgagTjOEEcp1y1Tr5h6f5HtfwDqAKiUCmmXPsqamspbGLp/NLz8dl1DssmyAut1xIiRpjkEWXom1+CuaHhvlYXoQnpey4bhD3i2O0C3RTJqw957hyZ9i778wAfxAXaubzKUUt4QXvOlXKS8bRc9HCNfv2B/HiI9fcmXcwgzxXcDLq2xsJnnOTD2Vfea5nlJaWq20hDa6d6i3MUwYsV9rFZrrNmfqz9n7NVN6AR/4Z2AvqK3AXo9B+q49GcdmHp2NQs238qQ0qiRDAcVLGVXRe+Jg54Qs/JpVfPlpEjYownnKs9QFwUM/RJWXwIGPxS0qmJCl4wwFQFHRz+0NFWFRI1YdwKpmdBuPc95P+xitPEfyHJNg6Unmp3BmgVwJKBIOhqs3QI6GqxCRsB46JBxLebpErBP0CQn6Gmw1jv2DjBkrGn7PmbH8ruLlCX1GRnK2jDb3R/O72eqZO14kdPqIuW/IywNJo2K4ckAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/2a542e63e9b9755604542de17e69f2c7/507e6/2026-01-05-mermaid3.webp 200w,
/static/2a542e63e9b9755604542de17e69f2c7/28a80/2026-01-05-mermaid3.webp 400w,
/static/2a542e63e9b9755604542de17e69f2c7/8d2ea/2026-01-05-mermaid3.webp 800w,
/static/2a542e63e9b9755604542de17e69f2c7/68fc1/2026-01-05-mermaid3.webp 1200w,
/static/2a542e63e9b9755604542de17e69f2c7/43d96/2026-01-05-mermaid3.webp 1600w,
/static/2a542e63e9b9755604542de17e69f2c7/49067/2026-01-05-mermaid3.webp 3750w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/2a542e63e9b9755604542de17e69f2c7/36ca5/2026-01-05-mermaid3.png 200w,
/static/2a542e63e9b9755604542de17e69f2c7/a3397/2026-01-05-mermaid3.png 400w,
/static/2a542e63e9b9755604542de17e69f2c7/a331c/2026-01-05-mermaid3.png 800w,
/static/2a542e63e9b9755604542de17e69f2c7/8537d/2026-01-05-mermaid3.png 1200w,
/static/2a542e63e9b9755604542de17e69f2c7/1a152/2026-01-05-mermaid3.png 1600w,
/static/2a542e63e9b9755604542de17e69f2c7/0bcb8/2026-01-05-mermaid3.png 3750w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/2a542e63e9b9755604542de17e69f2c7/a331c/2026-01-05-mermaid3.png&quot;
            alt=&quot;mermaid3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Multiple async processors:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each processor tries to acquire the exclusive lock,&lt;/li&gt;
&lt;li&gt;First one wins, others wait or skip,&lt;/li&gt;
&lt;li&gt;Guarantees that only one processor handles a projection at a time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e008a6d034bc882fe15fec2c63ec1410/faa17/2026-01-05-mermaid4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 102.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC4klEQVR42p1VyXKcMBDN/59TlUNO+Zmcc3LFsSczGRgWCdACCBBIL91iYrvKWxKqGgRSP71e9PgAuoIBZrnCiZmeHpuKwAq0wSObBmSDRT72KL3DGDZg80BfIpJ5lQMDjyuQFz7wPWhA5eRwkGjOGr4LwBQho8fRKNxVJQ5SoJpHqI12IuA4FFhtCVX8BDEhUjliCARI1zZGhCHCdROCo/HItAG7eohlQm40ajdAuBELM4y8oUIkc6qi9Q35aYbaAf9cbadgeot+HDGODl2rICqJu9sDzqcc5aWCtT2GgebdTOsNhGyTb4x4Dqg6nYC8XzHPPj2N6XF/f0JR1FDK0PcFy8JzHs45aKWv3vEFhsSo7wds25bAlmVJ73leomm6NGag3dYEbLR9CvGcITv1/R4yb8BgtxTy6ZQRaItpmpMx047W80avhsy78a7McF1344vzxqFL2aEsawq/oqdIKVAPIb8AyI4hxIf3eN1WE5g2AzE2qGuJqpSQok0RtI16msL3ABe6L8RcEkMJS1aWJYE2xHYP/80cPgKG/YP7ivb0EZe7z8hvP2EUXxB9lfIVqRfXlbvA/n3I2BpM5jvE5RtMewM/3hPS/DDNa9nnVUDOiaYQrB3Swr6fUdUGNzdHnH5RH2qHiarLHcDh8vq3q0z0uce4ynul17Ryr7KFEM1eZToxZWp0/X9VZjCeY0bcLhUZM9ur3P1Lla9tQ2lg4zPMbdOl1PAxfKfKzCSE8AzQch+2GpLAUtiVSMKwC8QLIbNkRQeMnSMp2qWMQ+hJ+3TcUA49MpKwU9tCEDvVdjBVhu5yhK6zq5SZK+CDwFoUBwF5VlhZsVlgqamPpsOPosCxodPhZ2jWQxJYTDU2J6HKI40lCezlUWAZYBIkRzUdfLnAt+zEv4AF56lPv4BstLjMQ2LNv4BgCmwEsnQZgr3QuEitkwBZgNlSpeJ1zKmgFdv1M3/a4pOm51yHq2N67nr4G7hgXOLo4AANAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/e008a6d034bc882fe15fec2c63ec1410/507e6/2026-01-05-mermaid4.webp 200w,
/static/e008a6d034bc882fe15fec2c63ec1410/28a80/2026-01-05-mermaid4.webp 400w,
/static/e008a6d034bc882fe15fec2c63ec1410/8d2ea/2026-01-05-mermaid4.webp 800w,
/static/e008a6d034bc882fe15fec2c63ec1410/68fc1/2026-01-05-mermaid4.webp 1200w,
/static/e008a6d034bc882fe15fec2c63ec1410/43d96/2026-01-05-mermaid4.webp 1600w,
/static/e008a6d034bc882fe15fec2c63ec1410/e9629/2026-01-05-mermaid4.webp 3320w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/e008a6d034bc882fe15fec2c63ec1410/36ca5/2026-01-05-mermaid4.png 200w,
/static/e008a6d034bc882fe15fec2c63ec1410/a3397/2026-01-05-mermaid4.png 400w,
/static/e008a6d034bc882fe15fec2c63ec1410/a331c/2026-01-05-mermaid4.png 800w,
/static/e008a6d034bc882fe15fec2c63ec1410/8537d/2026-01-05-mermaid4.png 1200w,
/static/e008a6d034bc882fe15fec2c63ec1410/1a152/2026-01-05-mermaid4.png 1600w,
/static/e008a6d034bc882fe15fec2c63ec1410/faa17/2026-01-05-mermaid4.png 3320w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/e008a6d034bc882fe15fec2c63ec1410/a331c/2026-01-05-mermaid4.png&quot;
            alt=&quot;mermaid4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. When you add a new projection:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No status row exists yet,&lt;/li&gt;
&lt;li&gt;Inlines skip automatically,&lt;/li&gt;
&lt;li&gt;First rebuild creates the row and backfills data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h2&gt;
&lt;p&gt;Just like making delicious soup, designing robust, fault-tolerant and performant distributed systems is not that easy. Building an event store is not that hard. But only if we exclude the async processing part.&lt;/p&gt;
&lt;p&gt;I hope this walkthrough covers both the conceptual and practical aspects of handling projection rebuilds.&lt;/p&gt;
&lt;p&gt;We used PostgreSQL and Advisory Locks, as PostgreSQL is cool and is a driving force in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;. But all the same principles apply to other tools and storage (with their specifics).&lt;/p&gt;
&lt;p&gt;I explained why advisory locks and status columns complement each other:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Advisory locks&lt;/strong&gt; handle the fast path (in-memory, no disk I/O for normal operations) and prevent races at transition points (rebuild can’t start until in-flight inlines finish)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Status column&lt;/strong&gt; handles crash recovery (persists across connection failures) and new projection bootstrapping (inlines skip until first rebuild completes)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Neither alone is sufficient. Together, they provide the guarantees we need without external infrastructure. Just PostgreSQL doing what PostgreSQL does.&lt;/p&gt;
&lt;p&gt;The cost on the hot path is microseconds: one in-memory lock check, one indexed read on a tiny cached table. For most systems, that’s an acceptable tradeoff.&lt;/p&gt;
&lt;p&gt;I hope this article also shows you how to use distributed locking in practice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please tell me your thoughts and concerns, especially if you see any blind spots in this design!&lt;/strong&gt; You can do that in our &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Emmett Discord&lt;/a&gt;, come on in, we have a nice community!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you’re dealing with such issues, I’m happy to help you through consulting or mentoring. &lt;a href=&quot;mailto:oskar@event-driven.io&quot;&gt;Contact me&lt;/a&gt; and we’ll find a way to unblock you!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Or check also other related resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/286&quot;&gt;Emmett’s Pull Request implementing described approach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/projections_and_read_models_in_event_driven_architecture/&quot;&gt;Guide to Projections and Read Models in Event-Driven Architecture&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/distributed-locking-a-practical-guide/&quot;&gt;Distributed Locking: A Practical Guide&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/consumers_processors_in_emmett/&quot;&gt;Consumers, projectors, reactors and all that messaging jazz in Emmett&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_scale_projections_in_the_event_driven_systems/&quot;&gt;How to scale projections in the event-driven systems?&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/checkpointing_message_processing/&quot;&gt;Checkpointing the message processing&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/lets_talk_about_positions_in_event_stores/&quot;&gt;Let’s talk about positions in event stores&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Architecture Weekly is 5 years old! 🎉]]></title><description><![CDATA[Welcome to the new week! Who said that LinkedIn notifications are useless? I did, several times. Yet! Today, I opened my computer for the…]]></description><link>https://event-driven.io/en/architecture-weekly-is-5-years-old/</link><guid isPermaLink="false">https://event-driven.io/en/architecture-weekly-is-5-years-old/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;Who said that LinkedIn notifications are useless? I did, several times. Yet! Today, I opened my computer for the first time since last Tuesday and just saw a notification that &lt;strong&gt;Architecture Weekly is 5 years old now!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/48a0f2e8bd9e7c19d003c1161acf35a1/9a246/2025-12-29-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAEEAgP/xAAVAQEBAAAAAAAAAAAAAAAAAAACA//aAAwDAQACEAMQAAABelLN9SUZ/8QAGRAAAgMBAAAAAAAAAAAAAAAAAQIAAxMh/9oACAEBAAEFAkHIR2twK7NUXcGf/8QAFREBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQMBAT8BZ//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABsQAAICAwEAAAAAAAAAAAAAAAABESECEDET/9oACAEBAAY/AhXpYvuXCfOIuWy6P//EABkQAAMBAQEAAAAAAAAAAAAAAAERIQAQMf/aAAgBAQABPyE94xmLhEXI46wUYfArAnAd03//2gAMAwEAAgADAAAAEC8P/8QAFhEBAQEAAAAAAAAAAAAAAAAAARAR/9oACAEDAQE/EMBP/8QAFhEBAQEAAAAAAAAAAAAAAAAAARAR/9oACAECAQE/EDVn/8QAGhABAAMBAQEAAAAAAAAAAAAAAQARMSFBYf/aAAgBAQABPxBhIU59nRNlNDpVdh3jiDiBVbrclMhltUusHm+SoLigWT//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/48a0f2e8bd9e7c19d003c1161acf35a1/507e6/2025-12-29-cover.webp 200w,
/static/48a0f2e8bd9e7c19d003c1161acf35a1/28a80/2025-12-29-cover.webp 400w,
/static/48a0f2e8bd9e7c19d003c1161acf35a1/8d2ea/2025-12-29-cover.webp 800w,
/static/48a0f2e8bd9e7c19d003c1161acf35a1/68fc1/2025-12-29-cover.webp 1200w,
/static/48a0f2e8bd9e7c19d003c1161acf35a1/43d96/2025-12-29-cover.webp 1600w,
/static/48a0f2e8bd9e7c19d003c1161acf35a1/0a2f3/2025-12-29-cover.webp 3088w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/48a0f2e8bd9e7c19d003c1161acf35a1/37402/2025-12-29-cover.jpg 200w,
/static/48a0f2e8bd9e7c19d003c1161acf35a1/4cda9/2025-12-29-cover.jpg 400w,
/static/48a0f2e8bd9e7c19d003c1161acf35a1/c60e9/2025-12-29-cover.jpg 800w,
/static/48a0f2e8bd9e7c19d003c1161acf35a1/6c738/2025-12-29-cover.jpg 1200w,
/static/48a0f2e8bd9e7c19d003c1161acf35a1/56dca/2025-12-29-cover.jpg 1600w,
/static/48a0f2e8bd9e7c19d003c1161acf35a1/9a246/2025-12-29-cover.jpg 3088w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/48a0f2e8bd9e7c19d003c1161acf35a1/c60e9/2025-12-29-cover.jpg&quot;
            alt=&quot;2025 12 29 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;At this point, you probably know that I’m not the anniversary-type-of-guy (my wife &lt;em&gt;loves&lt;/em&gt; that part of me…), but well, that’s a nice milestone!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Architecture Weekly started as a way to organise my readings.&lt;/strong&gt; I’m a curious guy, and I like to read a lot about different topics. During my career, I worked on a lot of weird projects, which exposed me to many technologies and approaches. It was not always pleasant, but it taught me that technology is a tool, and there’s a big diversity of tools that can lead you to a proper solution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, I had always had hundreds of open browser tabs until my Firefox crashed from time to time (yes, I’m a Firefox-type-of-guy).&lt;/strong&gt; I’m sure you know that too. I tried multiple ways to organise that: tools like Notion, e-mail threads, etc. None of them was perfect. At some point, my friends treated me like a librarian and asked me for interesting links on a topic or what interesting things I’d read recently. I decided to put my readings publicly into a Git repository and use Markdown, so I can easily share them. This repository still exists: &lt;a href=&quot;https://github.com/oskardudycz/ArchitectureWeekly&quot;&gt;https://github.com/oskardudycz/ArchitectureWeekly&lt;/a&gt;. At first, just links organised in groups.&lt;/p&gt;
&lt;p&gt;I started sharing it with my friends, and they told me it was useful, so I started sharing it with their friends. It seemed so useful to others that I shared it on the other channels, socials, and groups I was part of. Feedback was still positive, and some people started pushing me to create the newsletter, even though they could subscribe to GitHub release e-mail notifications; they still landed in the same notification swamp, as usual, with other notifications. So they asked if I could set up “a proper newsletter”. I was reluctant to do it, but well, as you already know, I did. I used Substack because it helped me avoid the usual accidental complexity by writing my own blogging engine from scratch.&lt;/p&gt;
&lt;p&gt;At some point, I decided to try making a paid version of the newsletter. I decided to build a Discord community for paid subscribers and my &lt;a href=&quot;https://github.com/sponsors/oskardudycz&quot;&gt;GitHub Sponsors&lt;/a&gt;. I started to also run webinars every month, also inviting guests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Complete list of webinars we had so far:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing&quot;&gt;#1 - From CRUD to Event Sourcing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short&quot;&gt;#2 - Keep your streams short! Or how to model Event-Sourced systems efficiently&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;#3 - Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice&quot;&gt;#4 - From CRUD to CQRS in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100&quot;&gt;#5 - Architecture Weekly 100 Edition - Live Q&amp;#x26;A&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;#6 - Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven&quot;&gt;#7 - Design and test Event-Driven projections and read models&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates&quot;&gt;#8 - Slim down your aggregates!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;#9 - Radek Maziarka - Modularization with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;#10 - PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski&quot;&gt;#11 - Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify&quot;&gt;#12 - Jeremy D. Miller: Simplify your architecture with Wolverine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic&quot;&gt;#13 - Yves Goeleven - The Fantastic 9&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-14-mateusz-jendza-why-verified&quot;&gt;#14 - Mateusz Jendza - Why Verified Credentials is the Future of Digital Identity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-15-mario-bittencourt-leveraging&quot;&gt;#15 - Mário Bittencourt: Leveraging BPMN for Seamless Team Collaboration in Software Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-1-sagas-hector-garcia&quot;&gt;#16 - Papers We Love #1 - Sagas (Hector Garcia-Molina, Kenneth Salem)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-16-simple-patterns-for-events&quot;&gt;#17 - Simple patterns for events schema versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-17-andrea-magnorsky-introducing&quot;&gt;#18 - Andrea Magnorsky: Introducing Bytesize Architecture Sessions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-18-laila-bougria-debug-your&quot;&gt;#19 - Laïla Bougriâ: Debug your thinking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;#20 - Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;#21 - Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-22-on-performance-testing/&quot;&gt;#22 - On Performance Testing with Jarosław Pałka&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;#23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/frontent-architecture-backend-architecture/&quot;&gt;#24 - Frontent Architecture, Backend Architecture or just Architecture? With Tomasz Ducin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/applying-observability-from-strategy/&quot;&gt;#25 - Applying Observability: From Strategy to Practice with Hazel Weakly&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/react-query-a-solution-for-frontend/&quot;&gt;#26 - React Query: A solution for Frontend State Management challenges? With Tomasz Ducin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/documenting-event-driven-architecture/&quot;&gt;#27 - Documenting Event-Driven Architecture with EventCatalog and David Boyne&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/practical-introduction-to-event-sourcing/&quot;&gt;#28 - Practical Introduction to Event Sourcing with Emmett&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;And now, yes, all of them are &lt;a href=&quot;/en/whole-architecture-weekly-content/&quot;&gt;available for free&lt;/a&gt;.&lt;/strong&gt; You can watch them. It’s around 50 hours of free knowledge. I believe that it is of much better quality than some paid courses. Why did I make them public?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At some point, I realised that I don’t have much fun with organising those links; it was more of a burden, as curating such lists takes a lot of time.&lt;/strong&gt; I’m also a perfectionist-type-of-guy, so I wanted to ensure that what I recommend is worth the read. It triggers the movement of brain cells. So if you read the article, you might disagree with it, but at least it’s sparking some thought. I also wanted to synthesise them and put my comments. It may seem like a simple thing to provide such articles. But try to keep it at the proper level week after week, and you’ll see it might not be as easy as it seems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There were not enough paid subscribers to justify the time spent on it, even though I was named a &lt;a href=&quot;/en/architecture_weekly_became_substack_bestseller/&quot;&gt;Substack Bestseller&lt;/a&gt;.&lt;/strong&gt; Yes, running a newsletter is not a great business plan. There are some exceptions to that, but even if you’re in the top few per cent, then it might not be enough to even justify the time spent on it.&lt;/p&gt;
&lt;p&gt;I decided to try a different angle and made Architecture Weekly a fully paid newsletter. Starting with a &lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;deep dive into Database Connection Pooling&lt;/a&gt;. I tried to see if I could grind for a while, providing high-quality content that’s not available elsewhere. I think I managed to deliver a lot of articles like that, and the paid subscriber count grew, but it was still not enough to justify my work. I enjoyed writing such articles a lot, and I still do, but it has put more pressure on me. And preparing such content takes time and effort. The effort I could spend elsewhere, e.g., with my family, in better-paid work like my &lt;a href=&quot;/en/training/&quot;&gt;workshops and consulting&lt;/a&gt;, or in my &lt;a href=&quot;https://github.com/event-driven-io/&quot;&gt;OSS projects&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And now, here we are, 10 months later and well, I still delivered a lot of deep dives, but shifted closer to the stuff I do daily, so helping other humans build systems and getting benefit from the Event-Driven approach. As you see, recently I cross-posted articles to my &lt;a href=&quot;/&quot;&gt;blog&lt;/a&gt; and here. I also used the angle of my work on tools like Emmett to explain general concepts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s next?&lt;/strong&gt; I’ll probably continue doing it as is; I might also consider merging the blog and newsletter into one. I even own domain event-driven.news, so maybe some rebranding will happen. But it’s not yet decided.&lt;/p&gt;
&lt;p&gt;I’ll definitely continue sharing what I learned, blogging, but the exact form? We’ll see. I’m open to suggestions! Comment under this post and tell me your thoughts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What type of content would you like to see here? Or is there some that you’d be even open to paying for?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Can’t promise that I’ll deliver all of them, but it’ll give me a food for thought.&lt;/p&gt;
&lt;p&gt;Nevertheless, it’s time for a small celebration!&lt;/p&gt;
&lt;p&gt;Thanks for being here with me through this journey!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Multi-tenancy and dynamic messaging workload distribution]]></title><description><![CDATA[There are several reasons why I’m blogging. The first one is that I forget, and writing helps me to remember and organise my findings. The…]]></description><link>https://event-driven.io/en/multitenant_and_dynamic_message_handling/</link><guid isPermaLink="false">https://event-driven.io/en/multitenant_and_dynamic_message_handling/</guid><content:encoded>&lt;p&gt;There are several reasons why I’m blogging.&lt;/p&gt;
&lt;p&gt;The first one is that I forget, and writing helps me to remember and organise my findings.&lt;/p&gt;
&lt;p&gt;The other is that I like to share my journey with the intention of sparing you, my dear reader, some of my struggles.&lt;/p&gt;
&lt;p&gt;Last but not least, it gives me the chance to learn from discussions inspired by them. It’s always a chance to meet new perspectives, correct what I did or just trigger some recollection.&lt;/p&gt;
&lt;p&gt;After the article about &lt;a href=&quot;/en/consumers_processors_in_emmett/&quot;&gt;consumer, processors and all that messaging jazz&lt;/a&gt;, there was a great discussion on &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Emmett Discord&lt;/a&gt;, there were a lot of interesting threads there, but the one that inspired what you read today came from the &lt;a href=&quot;https://ismaelcelis.com/&quot;&gt;Ismael Celis&lt;/a&gt; question. Ismael was curious about:&lt;/p&gt;
&lt;h2 id=&quot;distributing-the-workload-dynamically-between-processors&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#distributing-the-workload-dynamically-between-processors&quot; aria-label=&quot;distributing the workload dynamically between processors permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Distributing the workload dynamically between processors&lt;/h2&gt;
&lt;p&gt;And as that’s something I haven’t planned to deliver initially, but it’s a great question, let me put down some notes on my plan around it.&lt;/p&gt;
&lt;p&gt;Why would we want to distribute the workload dynamically? What does that even mean?&lt;/p&gt;
&lt;p&gt;Let’s start from the classical approach. We can define projections that will build read models based on the upcoming events. In &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;, it can look like that when &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html#read-models&quot;&gt;using Pongo as the storage tool&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cartsSummaryProjection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoSingleStreamProjection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  collectionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; shoppingCartsSummaryCollectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;getDocumentId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  canHandle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;initialState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;pending&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This means that when the projection handles only &lt;em&gt;ProductItemAdded&lt;/em&gt; and &lt;em&gt;ShoppingCartConfirmed&lt;/em&gt; event types. It’ll insert or update rows in the table based on the shopping cart id from the event data.&lt;/p&gt;
&lt;p&gt;We can plug this projection into a projector (the specific type of processor responsible for running projections).&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cartsSummaryProjector &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postgreSQLProjector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  processorId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shoppingCartSummary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  projection&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; cartsSummaryProjection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And plug it into the consumer:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postgreSQLEventStoreConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  processors&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    cartsSummaryProjector
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, consumers will poll the PostgreSQL event store and pipe filtered events by type into our projector.&lt;/p&gt;
&lt;p&gt;That’s simple, we know the filtering criteria, as consumers will know which event types their processor(s) can handle.&lt;/p&gt;
&lt;p&gt;Things get harder when we don’t know those criteria upfront. When can that happen? What if we were building an e-commerce SaaS product that lets shop owners buy a subscription to run their shops? Then we won’t know all of our tenants upfront. They will register as they go.&lt;/p&gt;
&lt;h2 id=&quot;what-options-do-we-have-for-such-a-multi-tenant-setup&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-options-do-we-have-for-such-a-multi-tenant-setup&quot; aria-label=&quot;what options do we have for such a multi tenant setup permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What options do we have for such a multi-tenant setup?&lt;/h2&gt;
&lt;p&gt;The simplest option would be to add a custom filter that allows filtering events on the consumer by the tenant.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; doesn’t support such a feature yet, but it could (and will at some point). We could extend our event metadata to contain the tenant and spin up a consumer for a dedicated tenant.&lt;/p&gt;
&lt;p&gt;We could even wrap it into a dedicated function.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tenantedConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  connectionString&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  tenant&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  processors&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MessageProcessor&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postgreSQLEventStoreConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    processors&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;filterBy&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tenant &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; tenant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    projection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course, this wouldn’t be fully dynamic, but we could make it dynamic at the infrastructure level, e.g., by passing the tenant ID from an environment variable and spinning up a new Container (e.g., a Kubernetes Pod).&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tenantedConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;PG_CONNECTION_STRING&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;TENANT_ID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; cartsSummaryProjector &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We could also start a new job from the API endpoint, running it in the existing deployment; there are plenty of options. You can go wild and think about other ways.&lt;/p&gt;
&lt;p&gt;Still, logically, they would look more or less like that. Either you’re scaling horizontally and sharding physically, tenants&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2174a833e6942562c7139d9698f9c168/064e6/2025-12-15-containers.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 80%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAC4jAAAuIwF4pT92AAACwUlEQVR42p1UzUsVURT3L2nTsoXrFi3aS9AqooISKmoh1C5olUQEGZWYkbowITUtBfFzUSgqouEXT5+RaPq0p75xZt583jt37vw69857ppIUDe9w3pxz7u/+ztdUGAULW7k8LNOBiBJEXGrhLAbo93l6G43dGbT0ZtHck8VOwYeME+0/HBsLwCn6qOBMgIVCO5SxLBGBq+fm41GcqnqHyssdOH3xPaYWd7U94omOk+pekSCRgOeFqFBAqfGoiBLg0MQmnrcv40XnEp61zSG/FwBJ6j8cqwBdN0gBj4MdiAqkw0lYgPtjGpJ7kPLPl2uGbvg3QAnKBvZ4K/It1xDtb0PI9KL/BEwgCZDnFuF87Yb0HcQyrdk/A6p6Fh2O3LYBu8hgOwJ5I8LKuoG13D4cT8D1ZUli8NL5JD6RYaKDTYdGgt5rm+dw4f4Arj4cQdW9fswsm+Bkt70ERRLGSoCaYXAyoOUmOr2aukmcudSJs9U9qLzShYlFQwNaTqJBy4DyOMPDY+D5gm6PdUpbuwEyqwYWsj/xfcOBYYbwAwkvOJryASBn6RyqcVA6DCL4NKCBz+g/Jx3CLBiIGMc+6Y31TdiWQ8sQHZDQM1tOWdBosDCGZblEP4Fpkg6l3gTXZSjaHoq0UiHF2Jan/YU9C5wA1YV6yIVa2TJDTTnGl/kQdb0RGvojrWdXhQbsGA3xdjhCE0n7WKwbkV2z0PjpGxq6lvCyYxljcwViSZviEEPFRvHtHBeorueoaeK4/opjZFZQnQQetAnces1x503qI8IYmtzCudsDuFE7jvN3B/GoZUGvqaMAhZD665HNJeibkRiZl+iblljbkZSmwPBsgu4JiY+TqZ1KiqU1G0/bMqj/sIInrRkMTuX1inoeAfoeg+9zqkeImDMIkjhiuhmqJpyF1JCyMG1TDWOhOvNbB9RM23TwC7nBqwF6HM0tAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/2174a833e6942562c7139d9698f9c168/507e6/2025-12-15-containers.webp 200w,
/static/2174a833e6942562c7139d9698f9c168/28a80/2025-12-15-containers.webp 400w,
/static/2174a833e6942562c7139d9698f9c168/8d2ea/2025-12-15-containers.webp 800w,
/static/2174a833e6942562c7139d9698f9c168/55867/2025-12-15-containers.webp 1117w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/2174a833e6942562c7139d9698f9c168/36ca5/2025-12-15-containers.png 200w,
/static/2174a833e6942562c7139d9698f9c168/a3397/2025-12-15-containers.png 400w,
/static/2174a833e6942562c7139d9698f9c168/a331c/2025-12-15-containers.png 800w,
/static/2174a833e6942562c7139d9698f9c168/064e6/2025-12-15-containers.png 1117w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/2174a833e6942562c7139d9698f9c168/a331c/2025-12-15-containers.png&quot;
            alt=&quot;containers&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Or you’re scaling vertically and running multiple tenants inside your box. Then, instead of spinning up new containers, you’re spinning up new jobs (processes, threads, virtual threads, etc.).&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/361518c616e35d85958f56a6a8ab62c8/99811/2025-12-15-threads.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 61.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAC4jAAAuIwF4pT92AAACr0lEQVR42l2Ty28NURzH+z9YsifCRizY27Bg0aBpRJNGLIhXIrGwQmhoPSpNRBdI03BVVDVe0YZICVpFH/R5pa329vbe6Z2ZO+87c+fjd+aWYJJvzpzvOed7fr/vd6YqV7CZWdBY1Bx0O6JQDLG9GNuvwHIjbDesjDJ3gpiiW2ZZ9imoM7ZfxvKQswFVGc3jxLUPHGzo49Clt9SdeU3PwCJ+EJK3YtzJPownZ3GmPqC78DNrcaTpHQfOv+Gw7K853Ut33xzqSQTH54qs251iQ02KTfsesHpHGy0dIximSTpjor1PkW3dy9L7DubzFoOjc6zfk2Jt9V0273/Iqm23uNQ+nAhqpk9V3gho7Zqg5cF3bj4a5/LdUYamDaIY7JKgkMedH8MxlpFuMaX1O0+nuZYa4fq9Ia7eG2VwvEAYr1SovJH3REAhkBXTDsjrHksFl5wRkbNgQXOZz+oUpArPj9BNC7NoJZWpM7YPhlRQZToRXikmiOR2cdaUFUeFIcH4pcpmy3bFApusZpLJCZYKCeYWcuhFX4RCCUsELREsSoXpLHye8ukfMxmZhaxRuXEyA58mFW/x7ae05IAblEnPGwxNLNI/PMP3H5pc5EhRoCtB1dLR1oDaRpe6qy7VFzxSfSE5M6a+2WNfk/BXPHY3eDwfjBKfdp18yfZjz6k+9ZIt9V20v0gnrS+b4uGSHnGwxaem0RNBj53nXNpehSzqMbVNXoL9IrhL+CcDZTJSzcaaDrbWd7Lj+DPWyFfR+CfloOLhm29l2RzR+zXi8cdIWi0nLffI/NmnKBm7hZ/NVz7qG51jXGwbovn+KBduf2FgbPmvlD1JOeafpyQBKZP/5/1QeVjh1dLvZfkHcMRDU6WsVHUrTH65wsqo5iq5P9wKr7iEF/MV9JVRQXWqGR6/AE9DU4aSSUX8AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/361518c616e35d85958f56a6a8ab62c8/507e6/2025-12-15-threads.webp 200w,
/static/361518c616e35d85958f56a6a8ab62c8/28a80/2025-12-15-threads.webp 400w,
/static/361518c616e35d85958f56a6a8ab62c8/8d2ea/2025-12-15-threads.webp 800w,
/static/361518c616e35d85958f56a6a8ab62c8/68fc1/2025-12-15-threads.webp 1200w,
/static/361518c616e35d85958f56a6a8ab62c8/59703/2025-12-15-threads.webp 1272w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/361518c616e35d85958f56a6a8ab62c8/36ca5/2025-12-15-threads.png 200w,
/static/361518c616e35d85958f56a6a8ab62c8/a3397/2025-12-15-threads.png 400w,
/static/361518c616e35d85958f56a6a8ab62c8/a331c/2025-12-15-threads.png 800w,
/static/361518c616e35d85958f56a6a8ab62c8/8537d/2025-12-15-threads.png 1200w,
/static/361518c616e35d85958f56a6a8ab62c8/99811/2025-12-15-threads.png 1272w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/361518c616e35d85958f56a6a8ab62c8/a331c/2025-12-15-threads.png&quot;
            alt=&quot;threads&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;That’s nothing special for Emmett; other tools also do this, e.g., &lt;a href=&quot;https://docs.spring.io/spring-kafka/reference/kafka/receiving-messages/message-listener-container.html#using-ConcurrentMessageListenerContainer&quot;&gt;Spring Boot Kafka concurrent listeners&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And hey, btw. there’s one more reason why I’m writing about my Emmett design: to let you benchmark your design against it. Even if you’re not planning to use it, then those are considerations for you either as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;internal tooling creator,&lt;/li&gt;
&lt;li&gt;user of other OSS tooling, to check how they solved it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Separating tenants through sharding give you:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;better option to scale and align the needs to the specific tenant workload,&lt;/li&gt;
&lt;li&gt;makes possible full separation in terms of networking, storage, etc.&lt;/li&gt;
&lt;li&gt;can be more costly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Separating tenants through partitioning the load inside one box:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;is usually cheaper,&lt;/li&gt;
&lt;li&gt;easier to manage,&lt;/li&gt;
&lt;li&gt;doesn’t give you a full separation and can fall into &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor&quot;&gt;Noisy Neighbour issue&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So there’s no golden rule to choose, it depends on your tooling, needs, etc.&lt;/p&gt;
&lt;p&gt;You can, of course, have a mixed solution, so running most of the small tenants in the same box, and the one with bigger security needs gets a special setup. Especially that dynamic split doesn’t have to be only per tenant. You can, e.g., have per region, per product range, per chain, etc.&lt;/p&gt;
&lt;p&gt;Also, such a dedicated setup can work for a set number of dynamic options. If you have 1000 tenants, would you spin up 1000 containers or jobs? You can, but just because you can doesn’t mean that you should. The more containers or threads you have, the more you pay for the coordination costs. At some point, it’s better to group processing into a manageable range of containers or threads.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ok, but how do we know which messages to process where? We could use a &lt;a href=&quot;https://en.wikipedia.org/wiki/Consistent_hashing&quot;&gt;consistent hash&lt;/a&gt;.&lt;/strong&gt; I wrote about it in detail in &lt;a href=&quot;/en/understanding-kafkas-consumer-protocol/&quot;&gt;Understanding Kafka’s Consumer Protocol: A Deep Dive into How Consumers Talk to Brokers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Kafka, by default, partitions its data on the producer side. The topic represents a logical split (e.g. all messages from the E-Commerce module), and the partition represents physical layout. Consumer groups receive messages. Kafka guarantees that precisely one consumer from the consumer group will receive messages from the specific partition. If we have fewer consumers than partitions, then of course, we can get more partitions to handle.&lt;/p&gt;
&lt;p&gt;The pseudo code for distributing load to consumers could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; partitionId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;partitionId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hash &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;consistentHashFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partitionId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumerId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hash &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; totalNumberOfConsumersWithinGroup&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In our case, we could use the tenant id as the partition id.&lt;/p&gt;
&lt;p&gt;Kafka’s strategy is also simpler than we might need. The partitioning is done on the producer side. The producer sends a message to the topic that already has a certain number of partitions:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hash &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;token function&quot;&gt;consistentHashFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;header&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;recordId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; partition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hash &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; totalNumberOfPartitionsWithinTopic&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In our case, if we’d like to allow distribution by any property, we’d need to load the message, read the field we want to partition/shard on, and send it to the appropriate container or thread.&lt;/p&gt;
&lt;p&gt;The mechanism can get pretty hefty. You’d need a more sophisticated mechanism to know where to spin up what, how to distribute the load between containers or threads, and to make it resilient. We’re getting into the area of distributed consensus algorithms such as &lt;a href=&quot;https://raft.github.io/&quot;&gt;Raft&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Paxos_(computer_science)&quot;&gt;Paxos&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do I want to go into this area with Emmett?&lt;/strong&gt; Definitely not for free! I don’t think that’d be even worth it, as we have mature solutions like Kafka, RabbitMQ, and other messaging systems that &lt;a href=&quot;https://cwiki.apache.org/confluence/display/KAFKA/KIP-500%3A+Replace+ZooKeeper+with+a+Self-Managed+Metadata+Quorum&quot;&gt;implement such algorithms and specialise in that&lt;/a&gt;. I’d prefer to make it easier to forward messages to them and let them do the work.&lt;/p&gt;
&lt;p&gt;What is definitely on the plate is the second option, which allows partitioned producers. You could define it as such:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cartsSummaryProjector &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postgreSQLProjector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  processorId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shoppingCartSummary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  projection&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; cartsSummaryProjection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;partitionBy&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tenant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then the consumer would poll messages from all tenants, forward them to projectors, and the projectors would internally spin up &lt;a href=&quot;https://nodejs.org/api/worker_threads.html&quot;&gt;worker threads&lt;/a&gt; per tenant or group them by consistent hashing.&lt;/p&gt;
&lt;p&gt;If we add to that distributed locking or &lt;a href=&quot;/en/checkpointing_message_processing/&quot;&gt;detecting conflicting checkpointing&lt;/a&gt; to ensure that there’s only one worker instance handling messages for the processor and partition id, then this should be good enough for the majority of cases.&lt;/p&gt;
&lt;p&gt;Keeping in mind the flexibility in which you can group (or not) projectors within consumers, you can define your own topologies.&lt;/p&gt;
&lt;p&gt;What are your thoughts? How do you deal with such cases? Would you like me to expand more on some cases?&lt;/p&gt;
&lt;p&gt;Or maybe you’d like to help me and &lt;a href=&quot;https://github.com/sponsors/event-driven-io&quot;&gt;sponsor my work in this area in Emmett&lt;/a&gt;? Then your project could also benefit faster from it!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Checkpointing the message processing]]></title><description><![CDATA[Let’s start by asking you two questions. What Super Frog has to do with messaging? When was the last time you wrote if statements in SQL? If…]]></description><link>https://event-driven.io/en/checkpointing_message_processing/</link><guid isPermaLink="false">https://event-driven.io/en/checkpointing_message_processing/</guid><content:encoded>&lt;p&gt;Let’s start by asking you two questions.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;What &lt;a href=&quot;https://en.wikipedia.org/wiki/Superfrog&quot;&gt;Super Frog&lt;/a&gt; has to do with messaging?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When was the last time you wrote if statements in SQL?&lt;/strong&gt; If it’s been a long time, have you at least seen them? If not, (don’t) worry, you’ll see them today.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Will it be a post about weird SQL usage? Not necessarily.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We’ll talk today about checkpointing our processing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b8e8597f7cafd3370a8b92a5055e61a3/a331c/2025-12-08-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAACwklEQVR42kVTyXLaQBDVfxjwJQYMSELghcUYkATakNiNFBvMLoIxXlJOKpVcklNOueSDX3okXDm8mu5p9evu1yPOVrNw1ByMugjtmsfm3sDD3MFqbMK/b2EztbGmcz1hsAKsCCzmv9/TGdxNW+BMRlTlYclZGDURrnOFvlGAWc+jpZyhpZ7/Pw+2JYcx+z12APM5vSZQsgS9KgSkWiWDoniMkhhDIRPBZeYotPljFAR2TxCiKIuhXRBi5MdQJLB4QFi+SKNJ47YUIqxloVOnTiOH7czE08pBW8tTdRF2IwuTijpNiZCDpYhQlBSdTLYsxUVwRo2HLJ9ClwUY1wKmlhQQt7Uz/Pm5wd/fX+D1i3j243jbZ+AQychNIHkXAT+KYDxMoUVFnvw0fn3PgGNjOg0JbarI9JzZebQPvtcuw3NK6GmXJAePHnUm6ymc3UQxFE/QufiAnBuFVmfTCTQFH44cLkQIRjWYfdi4TjAUHrUJSUF3ZiWLGhFKRHiXjuMmHwfvRg6E9FJILk6rhnqZ9XDLjioFmrACzNfLSTieEPhmlQiNNPhhBJNkAiMpgVP3CE3qnuWxCTn2ZGxyWKesMwYmPEOLitQbKRTlE7Kl4Du7SRKNTnAxiOK8G0P3JkE5fFCcgdtOTcw9FduphUY5RcQ8bTwD9SoFuZQKOrdquaCATZN0aNMdk0dZT6LSjGNgkGRK+OzYt9y3xwHWY4OeR5eSLqErRUzdBvYLC69+hxaUh1JKks7UOXXSpe2vbzXMhyo+U87SawZxkxphce5paWNxq+Nx2cZu2cPe9/D2MMSe7r/uBhgPKKGSR9e6ps4q9IRU/HgewZ+YeN308OL3oFZysLUy+nYN3IKqrcZWQHrvqtgt6L8ca2Tr9J+aePk0hD93sZkPsJgO8Lr1sJvT93cmZh917FcdrKY9bFcu1rM+/gGkWthtW9iddQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/b8e8597f7cafd3370a8b92a5055e61a3/507e6/2025-12-08-cover.webp 200w,
/static/b8e8597f7cafd3370a8b92a5055e61a3/28a80/2025-12-08-cover.webp 400w,
/static/b8e8597f7cafd3370a8b92a5055e61a3/8d2ea/2025-12-08-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/b8e8597f7cafd3370a8b92a5055e61a3/36ca5/2025-12-08-cover.png 200w,
/static/b8e8597f7cafd3370a8b92a5055e61a3/a3397/2025-12-08-cover.png 400w,
/static/b8e8597f7cafd3370a8b92a5055e61a3/a331c/2025-12-08-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/b8e8597f7cafd3370a8b92a5055e61a3/a331c/2025-12-08-cover.png&quot;
            alt=&quot;2025 12 08 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I’ve started my relationship with computers with games. I still have my Amiga 500. In those days, computers didn’t always have a hard disk. You’ve got a bunch of diskettes with different chapters of the game. Not all of them were simple games; many were quite sophisticated, and it took some time to finish them.&lt;/p&gt;
&lt;p&gt;Yet they were dealing with limited diskette space, so it was best if they didn’t have to use any of it. How can you then allow you to stop playing and return to the previous state? Or how to not force you to start from the beginning of the game when you fell from the platform, and well, you died? You died in the game, ofc, that at least you should be able to recover, right?&lt;/p&gt;
&lt;p&gt;As mentioned earlier, the limited space on diskettes and the additional complexity that came with it led many game makers to adopt a simple solution: checkpoints with codes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After you passed a level, you got a code you could type when you started the game, and instead of starting from the beginning, you could go directly to the place where you left off.&lt;/strong&gt; That worked pretty well for the platform and race car games, since your game’s storyline was always the same, immutable. If you had to go to level 27, the starting point and your character would always look the same. Of course, for RPG and strategy games, that’s a different story.&lt;/p&gt;
&lt;p&gt;Surprisingly, this parallel also matches the recovery from a business process failure.&lt;/p&gt;
&lt;p&gt;Let’s say we’re using message-based communication to streamline and make it more resilient. We don’t want to make it vulnerable to scenarios where we store information in one system, our process dies, and we don’t manage to notify the other parts.&lt;/p&gt;
&lt;p&gt;We’re using &lt;a href=&quot;/en/outbox_inbox_patterns_and_delivery_guarantees_explained/&quot;&gt;Outbox pattern&lt;/a&gt; to enable that technically. We’re storing messages in the relational table within the same transaction, updating the state after running business logic. Thanks to that, either both states are updated, and the message is scheduled, or none of it is. We’re getting (eventual) consistency thanks to that.&lt;/p&gt;
&lt;p&gt;Now we’re on the receiving end, so where we were in the previous article with the explanation of &lt;a href=&quot;/en/consumers_processors_in_emmett/&quot;&gt;Consumers, Processors and all that jazz&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s say that we’re using PostgreSQL and our Outbox structure looks as explained in the &lt;a href=&quot;/en/ordering_in_postgres_outbox/&quot;&gt;other article&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; outbox&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;-- the autoincremented position of the message to respect the order&lt;/span&gt;
   position        &lt;span class=&quot;token keyword&quot;&gt;BIGINT&lt;/span&gt;                    GENERATED &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IDENTITY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;-- used to detect gaps in numbering&lt;/span&gt;
   transaction_id  xid8                      &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;-- unique message id, which can be used for deduplication or idempotency&lt;/span&gt;
   message_id      &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;                      &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;-- the message type, e.g. `TransactionRecorded`&lt;/span&gt;
   message_type    &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;                      &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;-- serialised message data, e.g. to JSON&lt;/span&gt;
   &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;            JSONB                     &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;-- diagnostic information on when the message was scheduled&lt;/span&gt;
   scheduled       &lt;span class=&quot;token keyword&quot;&gt;TIMESTAMP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TIME&lt;/span&gt; ZONE  &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see, besides the message ID, type, and data, we’re also storing the (global) position number and transaction ID (to ensure we don’t skip in-flight transactions that have requested the global position number, read more &lt;a href=&quot;/en/ordering_in_postgres_outbox/&quot;&gt;here for reasoning&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Now we can be polling it with the query like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
     position&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message_type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;
     outbox
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; last_processed_transaction_id
               &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; position &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; last_processed_position&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction_id &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; last_processed_transaction_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; transaction_id &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; pg_snapshot_xmin&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pg_current_snapshot&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt;
    transaction_id &lt;span class=&quot;token keyword&quot;&gt;ASC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    position &lt;span class=&quot;token keyword&quot;&gt;ASC&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, thanks to that, we can have the global ordering guarantee on the receiving end. We’re trading a bit of performance for greater correctness. Not always acceptable, but for internal module communication or forwarding to the messaging system, that’s usually more than enough.&lt;/p&gt;
&lt;p&gt;That’s also the place where we’re back to our checkpointing. How do we know the last processed position?&lt;/p&gt;
&lt;p&gt;By default, that’s simple, we could either say:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;start from the beginning&lt;/strong&gt; and use -1 or some other hardcoded position,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;start from the end&lt;/strong&gt; and use the position of the last message in the table plus one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those cases can be fine when we need to handle all messages (e.g., adding a new process or reading a model), or when we don’t care about the past and need to process the newest notifications.&lt;/p&gt;
&lt;p&gt;Still, the reality is just like in the old games, we’d like to start somewhere in the middle, precisely where we left off.&lt;/p&gt;
&lt;p&gt;We need one more table for storing our checkpoints. It can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; processor_checkpoints
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;-- subscription name&lt;/span&gt;
   processor_id                     &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;      &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;-- information about the position of the last processed message&lt;/span&gt;
   last_processed_position          &lt;span class=&quot;token keyword&quot;&gt;INTEGER&lt;/span&gt;   &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;-- used to detect gaps in numbering&lt;/span&gt;
   last_processed_transaction_id    xid8      &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Simple stuff: the processor ID should be unique and reflect the processor’s logical name and the last processed position(s).&lt;/p&gt;
&lt;p&gt;If that looks simple, then let me follow up with the next set of potentially simple questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to store it?&lt;/li&gt;
&lt;li&gt;When to store it?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your answer is: “just do upsert statement”, then you’re kinda right, but that wouldn’t be simple, it’d be over-simplification. At least if you’d like to run it in production.&lt;/p&gt;
&lt;p&gt;Let’s start with how. And, for that, let me bring you now the promised stored procedure with if statements, it’s a bit simplified version from Emmett:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;REPLACE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FUNCTION&lt;/span&gt; store_processor_checkpoint&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  p_processor_id      &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  p_position          &lt;span class=&quot;token keyword&quot;&gt;BIGINT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  p_expected_position &lt;span class=&quot;token keyword&quot;&gt;BIGINT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  p_transaction_id    xid8
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;RETURNS&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; $$
&lt;span class=&quot;token keyword&quot;&gt;DECLARE&lt;/span&gt;
  current_position &lt;span class=&quot;token keyword&quot;&gt;BIGINT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;-- Handle the case when p_check_position is provided&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; p_expected_position &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;-- Try to update if the position matches p_check_position&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; processor_checkpoints
      &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;last_processed_position&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_position&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;last_processed_transaction_id&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_transaction_id
      &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;processor_id&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_processor_id &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;last_processed_position&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_check_position&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; FOUND &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;-- Successfully updated&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;-- Retrieve the current position&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;last_processed_position&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; current_position
      &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; processor_checkpoints
      &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;processor_id&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_processor_id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;-- Return appropriate codes based on current position&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; current_position &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_position &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;-- Idempotent check: position already set&lt;/span&gt;
      ELSIF current_position &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; p_expected_position &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;-- Failure: current position is greater&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;ELSE&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;-- Default failure case for mismatched positions&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;-- Handle the case when p_check_position is NULL: Insert if not exists&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; processor_checkpoints &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;processor_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;last_processed_position&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;last_processed_transaction_id&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p_processor_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p_position&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p_transaction_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;-- Successfully inserted&lt;/span&gt;
  EXCEPTION &lt;span class=&quot;token keyword&quot;&gt;WHEN&lt;/span&gt; unique_violation &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;-- If insertion failed, it means the row already exists&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;last_processed_position&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; current_position
      &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; processor_checkpoints
      &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;processor_id&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_processor_id&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; current_position &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; p_position &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;-- Idempotent check: position already set&lt;/span&gt;
      ELSIF current_position &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; p_expected_position &lt;span class=&quot;token keyword&quot;&gt;THEN&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;-- Insertion failed, row already exists with a greater position&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;ELSE&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;RETURN&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;-- Default failure case for mismatched positions&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
$$ &lt;span class=&quot;token keyword&quot;&gt;LANGUAGE&lt;/span&gt; plpgsql&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Oooh, even by copy and pasting, I’m already tired; there’s a fair reason why we’re not doing that too often nowadays.&lt;/p&gt;
&lt;p&gt;Let me untangle that for you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We’re trying to update the existing position or insert it if we’re storing it for the first time.&lt;/li&gt;
&lt;li&gt;If all went fine, we’re returning 1 as a result to denote complete success.&lt;/li&gt;
&lt;li&gt;If we saw that the checkpoint in the database had the same value, we’re returning 0.&lt;/li&gt;
&lt;li&gt;If we saw that the checkpoint is different from what was expected and further away from it, then we’re returning 2.&lt;/li&gt;
&lt;li&gt;Otherwise, we’re returning 3, which means that the checkpoint is different from the expected and older.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Essentially, by passing the expected position, we can detect whether we:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;already handled the specific position,&lt;/li&gt;
&lt;li&gt;have some competing instance of our processor handling our data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s why we’re doing this fancy dance with IF statements and a stored procedure.&lt;/p&gt;
&lt;p&gt;Detection assumes that we have a global ordering processing guarantee (thus, tricky bits with transaction ID).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It also shows why global ordering is useful.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By detecting that we’ve already handled a specific position, we can skip processing handling idempotency on the processor level.&lt;/p&gt;
&lt;p&gt;By detecting that there’s another processor with the same id processing messages, we can make it more resilient and detect the &lt;em&gt;noisy neighbour&lt;/em&gt; issue.&lt;/p&gt;
&lt;p&gt;How would that look in the code?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleBatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;messageBatch&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RecordedMessage&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProcessorContext&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BatchHandlingResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; checkpoint &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; messageBatch&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;messageBatch&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; message &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; messageBatch&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;onMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;      
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// No error was thrown: proceed to store checkpoint of the last processed message&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;storeProcessorCheckpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;transaction&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      processorId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processorId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      newCheckpoint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; checkpoint&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      lastProcessedCheckpoint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastProcessedCheckpoint&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; transaction&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;commit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// no need to do here, either we already handled it&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// or we have a mismatch of expected and existing checkpoints&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; transaction&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rollback&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProcessorContext&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  processorId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  lastProcessedCheckpoint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; bigint &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;onMessage&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AnyMessage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  pool&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConnectionPool&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BatchHandlingResult&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      newCheckpoint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; bigint &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;IGNORED&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;FURTHER&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;OLDER&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;storeProcessorCheckpoint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  execute&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SQLExecutor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    processorId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    newCheckpoint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; bigint &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
    lastProcessedCheckpoint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; bigint &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    partition&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;BatchHandlingResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; result &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;single&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;SELECT store_processor_checkpoint(
            &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;processorId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;,
            &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;newCheckpoint&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;,
            &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastProcessedCheckpoint&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;,
            pg_current_xact_id()
        ) as result;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; newCheckpoint&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;newCheckpoint&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; success&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;IGNORED&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;FURTHER&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;OLDER&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see, thanks to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;global ordering,&lt;/li&gt;
&lt;li&gt;checkpoint detection,&lt;/li&gt;
&lt;li&gt;storing checkpoint where our side effects will be stored,&lt;/li&gt;
&lt;li&gt;transactional capabilities of our end storage,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can ensure that the entire batch is processed or not. We could even optimise it by storing the checkpoint first and not processing the business logic if there’s a mismatch, then committing only if the logic succeeds.&lt;/p&gt;
&lt;p&gt;We’re getting by that generic idempotence check and detection of the noisy neighbour.&lt;/p&gt;
&lt;p&gt;Of course, I still believe that &lt;a href=&quot;/en/idempotent_command_handling/&quot;&gt;idempotence check should happen on the business logic side&lt;/a&gt;. But why not both?&lt;/p&gt;
&lt;p&gt;Being able to detect a noisy neighbour can help you automatically stop (or pause) one of the competing consumers and avoid inconsistency conflicts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are the tradeoffs of this approach?&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;This will work if we have a global ordering guarantee. Not many messaging solution gives us such. If we have a subscription-based outbox as explained, event store like &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;, Marten or KurrentDB, Kafka, this will work, but not necessarily for solutions like RabbitMQ, SQS, Google Pub Sub, etc.&lt;/li&gt;
&lt;li&gt;This works best if you have transaction capabilities. Batching updates generally improves performance, but sometimes can lead to long-lived transactions; beware of that. The subscription-based solution with a transaction ID also works best if your transactions are short. If they’re open for a long time, it can cause delays.&lt;/li&gt;
&lt;li&gt;Still, even without transactions, it will work fine, as long as you’re fine with having retries more often. If the business logic fails and the checkpoint is not committed, it’ll reprocess the already-handled messages from the previously stored messages. Which means that they can be handled more than once, but you should not lose any messages. You may also not fully benefit from the idempotence check for skipping already handled messages.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As always, the choice is yours.&lt;/p&gt;
&lt;p&gt;Still, I hope that this article will show you why:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;having a global ordering guarantee can be useful,&lt;/li&gt;
&lt;li&gt;why and how to checkpoint your processing, how they relate to level codes from old games like Super Frog,&lt;/li&gt;
&lt;li&gt;What are the tradeoffs, and how to consider them,&lt;/li&gt;
&lt;li&gt;…and that SQL IF statements are sometimes justified. But don’t go wild with them!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;And hey, I also hope that’s not something that you’d like to maintain on your own. There are mature tools to deal with such stuff, like Emmett, which implements this for you.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What are your thoughts? Questions? Concerns?&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Consumers, projectors, reactors and all that messaging jazz in Emmett]]></title><description><![CDATA[Did you know that you can build an event store in one hour? I even did it a few times on the conference stage. Actually, it took me usually…]]></description><link>https://event-driven.io/en/consumers_processors_in_emmett/</link><guid isPermaLink="false">https://event-driven.io/en/consumers_processors_in_emmett/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Did you know that you can build an event store in one hour?&lt;/strong&gt; I even did it a few times on the conference stage. Actually, it took me usually around 25 minutes; the rest was mistyping, lame jokes and a bit of explanation. See:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/gaoZdtQSOTo?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yet, my final thought was: Kids don’t do it at home.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It’s a fun coding exercise, but using the outcome in production? Not as much fun running and maintaining it. Why though? How hard can it be?&lt;/p&gt;
&lt;p&gt;Event Sourcing systems have two phases: appending events and processing them afterwards. The write side gets most of the attention in tutorials and talks - commands, deciders, event stores, optimistic concurrency, as you saw, I’m also one to blame.&lt;/p&gt;
&lt;p&gt;Providing the guarantees on the write side is relatively simple, especially if you use a database like PostgreSQL as a storage. You need to provide features like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;appending an event at the end of the stream,&lt;/li&gt;
&lt;li&gt;reading all events from the stream,&lt;/li&gt;
&lt;li&gt;a guarantee of the ordering within the stream,&lt;/li&gt;
&lt;li&gt;being able to read your writes,&lt;/li&gt;
&lt;li&gt;strong-consistent, atomic writes and optimistic concurrency.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That can be solved with knowledge about transactions, database design, etc. So again, why so hard?&lt;/p&gt;
&lt;p&gt;The processing side is where systems often struggle as they grow. This is where the Event Sourcing solution becomes an Event-Driven Messaging tool. And if you’ve read my previous articles, you know that this can be tricky at times.&lt;/p&gt;
&lt;p&gt;How do you reliably process events to build read models? How do you trigger side effects without losing messages? How do you scale processing independently from writes? How do you make it performant and run multiple handlers in parallel?&lt;/p&gt;
&lt;p&gt;I’ve been working on the message processing architecture in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; for a while now. I’ll try to explain how I designed the split between Consumers and Processors, the problems it solves, and the tradeoffs involved.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/14483a35803bc4c472795101685aa51a/b8a4c/Consumers-Processors.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 85.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAARABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAECAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB9yzUclWaosB//8QAGBAAAgMAAAAAAAAAAAAAAAAAARAAESD/2gAIAQEAAQUCVwFDH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABYQAAMAAAAAAAAAAAAAAAAAAAEQIP/aAAgBAQAGPwJG/wD/xAAbEAEBAAIDAQAAAAAAAAAAAAABADFRECFBcf/aAAgBAQABPyEJhL291Bqfks5zHP8A/9oADAMBAAIAAwAAABDLBzz/xAAXEQADAQAAAAAAAAAAAAAAAAAAARBx/9oACAEDAQE/EBbf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAHRABAAICAgMAAAAAAAAAAAAAAQARITEQQVFxof/aAAgBAQABPxB27TdkuGOvAS4LUw0iBbYayQWYt8iXvpbKnf3wz//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/14483a35803bc4c472795101685aa51a/507e6/Consumers-Processors.webp 200w,
/static/14483a35803bc4c472795101685aa51a/28a80/Consumers-Processors.webp 400w,
/static/14483a35803bc4c472795101685aa51a/8d2ea/Consumers-Processors.webp 800w,
/static/14483a35803bc4c472795101685aa51a/68fc1/Consumers-Processors.webp 1200w,
/static/14483a35803bc4c472795101685aa51a/43d96/Consumers-Processors.webp 1600w,
/static/14483a35803bc4c472795101685aa51a/002ca/Consumers-Processors.webp 4329w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/14483a35803bc4c472795101685aa51a/37402/Consumers-Processors.jpg 200w,
/static/14483a35803bc4c472795101685aa51a/4cda9/Consumers-Processors.jpg 400w,
/static/14483a35803bc4c472795101685aa51a/c60e9/Consumers-Processors.jpg 800w,
/static/14483a35803bc4c472795101685aa51a/6c738/Consumers-Processors.jpg 1200w,
/static/14483a35803bc4c472795101685aa51a/56dca/Consumers-Processors.jpg 1600w,
/static/14483a35803bc4c472795101685aa51a/b8a4c/Consumers-Processors.jpg 4329w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/14483a35803bc4c472795101685aa51a/c60e9/Consumers-Processors.jpg&quot;
            alt=&quot;Consumers &amp;amp; Processors&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-split-consumers-and-processors&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-split-consumers-and-processors&quot; aria-label=&quot;why split consumers and processors permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Split Consumers and Processors?&lt;/h2&gt;
&lt;p&gt;When processing messages, we already know that someone produced them. We’re on the receiving end. Facts are already known; now we need to do something about them.&lt;/p&gt;
&lt;p&gt;When we process them, do we care about the source? Typically, we take the information it gathers and reason about it. For instance, when we received an event indicating that a room reservation was made, we may need to send an email with details to the consumer, update the reservations dashboard, and generate a pro forma invoice. We may have specific logic, depending whether it came from our internal reservation platform or Booking.com, but we know the source from the message payload.&lt;/p&gt;
&lt;p&gt;That seems obvious, but it was an important realisation for me. When we’re building a read model in MongoDB, we don’t care if events come from PostgreSQL event store, EventStoreDB, RabbitMQ queue or Kafka topic.&lt;/p&gt;
&lt;p&gt;It needs events and the projection logic. Of course, it needs to know the guarantees around: delivery, ordering, idempotency, etc., but besides that? The message’s source doesn’t matter to its logic.&lt;/p&gt;
&lt;p&gt;Similarly, a component polling PostgreSQL for messages to publish them doesn’t care what happens to those events - whether they update read models or trigger webhooks is irrelevant to polling logic.&lt;/p&gt;
&lt;p&gt;These concerns are orthogonal.&lt;/p&gt;
&lt;p&gt;I realised that much of the complexity comes from coupling those two together. We wouldn’t like to change our processing logic because of an internal change in how they’re produced, or vice versa. I concluded that separating them means each can evolve independently. And I came with the initial idea for the split: Consumers and Message Processors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consumers&lt;/strong&gt; are responsible for getting messages from a source and forwarding them to processors. Think of them as the “delivery mechanism.” They handle the “where do events come from” concern. A consumer might connect to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A PostgreSQL event store, polling the events table,&lt;/li&gt;
&lt;li&gt;EventStoreDB, using push-based catch-up subscriptions,&lt;/li&gt;
&lt;li&gt;Kafka, consuming from topics,&lt;/li&gt;
&lt;li&gt;Any other message source you might have.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Processors&lt;/strong&gt; are responsible for doing something meaningful with those messages. They handle the “what do we do with messages” concern. A processor might:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update a read model in PostgreSQL or MongoDB,&lt;/li&gt;
&lt;li&gt;Call an external API when certain events occur,&lt;/li&gt;
&lt;li&gt;Publish events to Kafka or fire webhooks,&lt;/li&gt;
&lt;li&gt;Trigger workflow steps or saga operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This separation follows Unix philosophy: small, focused components connected by simple interfaces. Each piece does one thing well. You can plug any processor into any consumer. This gives you flexibility that matters in practice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can run the same projection logic against different event sources&lt;/li&gt;
&lt;li&gt;Adding new processors doesn’t require changing consumer code&lt;/li&gt;
&lt;li&gt;You can test processors in isolation with fake event streams&lt;/li&gt;
&lt;li&gt;Consumers and processors can be scaled independently&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let me show you how this looks in practice.&lt;/p&gt;
&lt;h2 id=&quot;how-consumers-work&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-consumers-work&quot; aria-label=&quot;how consumers work permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How Consumers Work&lt;/h2&gt;
&lt;p&gt;I deliberately decided to keep Consumers as dumb as possible. A consumer’s entire job is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Connect to a message source,&lt;/li&gt;
&lt;li&gt;Poll messages in batches or subscribe to notifications (depending on the source specifics).&lt;/li&gt;
&lt;li&gt;Forward them to all registered processors.&lt;/li&gt;
&lt;li&gt;Go back to step 2.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That’s it. No business logic. No complex state management. No decision-making about what to do with messages. Consumers are essentially routers.&lt;/p&gt;
&lt;p&gt;Why this simplicity? I’m a simple guy; I like clear boundaries for responsibility. They help me reason about both how to use the tool and how to handle it when things go wrong. When message delivery breaks, you want to know exactly where to look. With a simple consumer, the question is binary: did it deliver messages or didn’t it? There’s no complex interaction between delivery logic and processing logic to untangle at 3 AM.&lt;/p&gt;
&lt;p&gt;For EventStoreDB, the consumer creates a single subscription and fans out messages to all registered processors. For PostgreSQL, it polls the message table in batches, handling ordering guarantees (we’ll get to why that later).&lt;/p&gt;
&lt;p&gt;Here’s what a basic consumer setup looks like for PostgreSQL :&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postgreSQLEventStoreConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  processors&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    shoppingCartDetailsProjector&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    customerAnalyticsProjector&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    orderNotificationReactor
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; consumer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Set up looks accordingly for the other sources providing options specific for the source, e.g. for EventStoreDB store, you may want to provide the category stream name:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;eventStoreDBEventStoreConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  from&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; stream&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;$ce-roomRservations&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; resolveLinkTos&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The consumer receives messages, batches them and forwards them to all processors. Each processor handles messages independently - they don’t know about each other, and they don’t need to.&lt;/p&gt;
&lt;h2 id=&quot;why-batching-belongs-to-consumers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-batching-belongs-to-consumers&quot; aria-label=&quot;why batching belongs to consumers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Batching Belongs to Consumers&lt;/h2&gt;
&lt;p&gt;Who decides how many messages to fetch at once? That’s a tricky question. I think that batching can happen both at the consumer and processor levels. The consumer decides the batch size for the polling or receiving to tune the receiving throughput. The processor can either align with it, using those batches as a safe default or diverge to its specifics. Read more on &lt;a href=&quot;/en/batching_async_enumerable/&quot;&gt;Why you should batch message processing in my other article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Different message sources have different optimal batch sizes. PostgreSQL might be efficient with 100-row fetches. EventStoreDB subscriptions don’t have built-in batching; they deliver events as they arrive. Kafka has its own batching semantics. These are all source-specific optimisations we should be able to apply without ending up with the lowest common denominator.&lt;/p&gt;
&lt;p&gt;Processors, by default, can just receive batches and process them. Then they can decide whether to split batches into smaller chunks, group them into even larger chunks, or process them as single messages. For instance, PostgreSQL can handle random single updates pretty well, whereas &lt;a href=&quot;/en/projecting_from_marten_to_elasticsearch/&quot;&gt;Elastic prefers batching updates&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-processors-work&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-processors-work&quot; aria-label=&quot;how processors work permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How Processors Work&lt;/h2&gt;
&lt;p&gt;If consumers are simple routers, processors are where the interesting work happens. They’re the smarter ones in this relationship. A processor is responsible for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Processing logic&lt;/strong&gt;: Actually doing something useful with events. Updating a read model, sending an email, calling an API.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checkpointing&lt;/strong&gt;: Tracking which messages have been processed. This is crucial - without it, you’d reprocess everything from the beginning every time you restart.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error handling&lt;/strong&gt;: Deciding what to do when processing fails. Retry? Skip? Stop everything?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Idempotency&lt;/strong&gt;: Doing their best to ensure that reprocessing the same event doesn’t cause problems. Of course, still assuming that handlers should be idempotent, read more in &lt;a href=&quot;/en/idempotent_command_handling/&quot;&gt;my other article&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backpressure:&lt;/strong&gt; They need to be able to tell consumers that they cannot process more messages at the moment, and that the consumer needs to slow down delivery.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are general promises and common stuff for the message processing logic. Still, there are multiple reasons why you want to process incoming messages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Read models (projections)&lt;/strong&gt; transform events into queryable state. For instance, shopping cart events - &lt;em&gt;ProductItemAdded&lt;/em&gt;, &lt;em&gt;ProductItemRemoved&lt;/em&gt;, &lt;em&gt;ShoppingCartConfirmed&lt;/em&gt; - need to become a document showing current items, quantities, and totals. Something your API can quickly return when a user opens their cart.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reactions&lt;/strong&gt; trigger side effects after a business fact has happened. When a shopping cart is confirmed, you may want to send a confirmation email, notify the shipment module, and register a new order. These things need to happen, but they’re not part of the core business logic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workflows&lt;/strong&gt; coordinate multi-step processes across multiple streams. An order might involve payment processing, inventory reservation, and shipping coordination - each with its own state and events.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integration&lt;/strong&gt; means forwarding events to other systems. Other services in your systems might need to know about orders. External partners might need webhook notifications. You might publish to messaging systems for downstream consumers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of those processing needs a bit different ways to handle reliability, ordering, throughput, etc. Also, all tools we integrate with require a different approach: storing the read model in PostgreSQL will be &lt;em&gt;quite&lt;/em&gt; different from forwarding a message to Kafka.&lt;/p&gt;
&lt;p&gt;I wouldn’t like to handwave all of those specifics and end up with the lowest common denominator. That’s why I decided to group them into the following &lt;em&gt;archetypes&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;projectors,&lt;/li&gt;
&lt;li&gt;reactors,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/workflow-engine-design-proposal-tell/&quot;&gt;workflows&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;allow custom message processors to allow people to tune it fully to their needs,&lt;/li&gt;
&lt;li&gt;and in the future, stuff like forwarders, web hooks and others we find useful.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of them should have a unified API that allows them to be plugged into different consumers, while also embracing differences in message processing and target API specifics.&lt;/p&gt;
&lt;p&gt;That’s also why each message processing target (PostgreSQL, EventStoreDB, MongoDB, InMemory, Kafka, SQS, etc.) will have its own implementations.&lt;/p&gt;
&lt;p&gt;I believe that this focused responsibility, different archetypes, and specific implementations for different tools will strike the right balance between reusability and avoiding the lowest common denominator. We’ll see if that’s not a famous last words.&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/257/files&quot;&gt;My RFC for Workflow Processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/compilation-isnt-just-for-programming/&quot;&gt;How message pipelines can be technically implemented&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The example projector can look like that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; projection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoSingleStreamProjection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  collectionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; shoppingCartsSummaryCollectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  canHandle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;initialState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;pending&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; postgreSQLProjector &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postgreSQLProjector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; projection &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reactor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;postgreSQLReactor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    processorId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;order-notifications&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    canHandle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;eachMessage&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
      emailService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendOrderConfirmation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;    
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;native-implementations-of-processors&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#native-implementations-of-processors&quot; aria-label=&quot;native implementations of processors permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Native implementations of processors&lt;/h2&gt;
&lt;p&gt;Different storage requirements require different capabilities, and getting proper guarantees might involve deeper knowledge. For instance, &lt;a href=&quot;/en/ordering_in_postgres_outbox/&quot;&gt;Postgres sequences issues can impact your messaging guarantees&lt;/a&gt;. Those are cases where, when you’re starting, you might not anticipate. Test environments may not even catch it; you might realise you’re losing business data when you reach production. That’s why it’s, imho, better to have a tool that solves it rather than trying to maintain it on your own, making technical infrastructure something you need to keep working on instead of your business features. How does &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; solve them? Let’s discuss them briefly. I’ll try to expand in the future posts about the details.&lt;/p&gt;
&lt;h2 id=&quot;resilience&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#resilience&quot; aria-label=&quot;resilience permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Resilience&lt;/h2&gt;
&lt;p&gt;What happens if the processor fails? By default, it stops processing. But only this one,  the consumer keeps pushing events to the other processors that can continue.  Consumer stops when all their processors are inactive.&lt;/p&gt;
&lt;p&gt;Why? Consider this scenario: you have two processors, one updating MongoDB and another updating PostgreSQL. MongoDB becomes temporarily unavailable. Should that stop PostgreSQL updates?&lt;/p&gt;
&lt;p&gt;Still, failure behaviour is configurable; your message handler can return:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;void/ACK&lt;/strong&gt;: message processed successfully, continue to the next one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SKIP&lt;/strong&gt;: Skip this message, useful for poison messages that consistently fail&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;STOP&lt;/strong&gt;: Stop this processor entirely.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Why have &lt;em&gt;skip&lt;/em&gt; separate from &lt;em&gt;ACK&lt;/em&gt;?  Consider a poison message - a message that causes your processor to fail every time. Without &lt;em&gt;skip&lt;/em&gt;, you have two bad options: fail forever (blocking all processing) or &lt;em&gt;ACK&lt;/em&gt; it (pretending you processed it). With &lt;em&gt;Skip&lt;/em&gt;, you can move it to a dead-letter queue for investigation while continuing to process other messages.&lt;/p&gt;
&lt;p&gt;For now, &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; doesn’t support Dead Letter/Poison Message Queues out of the box, but they will be supported in the future. You could already append those events to some specific stream.&lt;/p&gt;
&lt;p&gt;In upcoming releases, we’ll also have configurable retry policies based on error type and other factors. Just like we already have for command handlers (e.g. to retry 3 times with exponential backoff for Optimistic Concurrency error).&lt;/p&gt;
&lt;p&gt;There’s no easy answer to when to stop and when to skip poison messages. Neither choice is universally correct. A financial system might need all-or-nothing semantics. A social media feed can tolerate inconsistency between views.&lt;/p&gt;
&lt;p&gt;That’s also why you can freely group processors within consumers. Best if they share similar resiliency and desired throughput characteristics. If they’re very different, you can always spin up another consumer for the same source and process it differently.&lt;/p&gt;
&lt;h2 id=&quot;checkpointing-processing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#checkpointing-processing&quot; aria-label=&quot;checkpointing processing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Checkpointing processing&lt;/h2&gt;
&lt;p&gt;In Emmett, &lt;strong&gt;processors own their checkpoints&lt;/strong&gt;. Each processor independently tracks the last message it processed. The consumer doesn’t maintain any checkpoint state.&lt;/p&gt;
&lt;p&gt;When a consumer starts up, it asks all registered processors for their last processed position and starts polling from the earliest one.&lt;/p&gt;
&lt;p&gt;It has several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Independent progress&lt;/strong&gt;: Processors can move at different speeds. If your MongoDB projector is fast and your analytics processor can get slow at times, they each track their own progress. The slow one doesn’t hold back the fast one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Isolated failures&lt;/strong&gt;: If one processor’s checkpoint storage fails, only that processor is affected. Others continue working.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy replay&lt;/strong&gt;: To rebuild a single projection, you just reset that processor’s checkpoint. No need to coordinate with other processors or manage a global position.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;: Processors can store checkpoints wherever makes sense - in the same database as their read model, in a separate checkpoint table, or anywhere else.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capability to redistribute the load.&lt;/strong&gt; As mentioned in the previous points, if you observe that one of the processors is slower or demands more resources, you can freely deploy it separately in a different consumer, and it’ll start where it left off.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The tradeoff? When a consumer restarts, it might poll events that most processors have already seen. If one processor is significantly behind, all processors receive those events again (they just skip them based on their checkpoints). This is why you should group processors by their typical processing pace - don’t put a real-time dashboard projector and a monthly analytics batch processor on the same consumer.&lt;/p&gt;
&lt;h2 id=&quot;backpressure-when-processors-cant-keep-up&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#backpressure-when-processors-cant-keep-up&quot; aria-label=&quot;backpressure when processors cant keep up permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Backpressure: When Processors Can’t Keep Up&lt;/h2&gt;
&lt;p&gt;Backpressure occurs when processors can’t process messages fast enough for the consumer to deliver them. This is a real operational concern that needs explicit handling.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; doesn’t support it at the moment, but here’s what I’m thinking about it.&lt;/p&gt;
&lt;p&gt;There are several strategies, each with tradeoffs:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Ignore backpressure&lt;/strong&gt;: Consumer keeps polling and pushing regardless of processor state.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pro: Simple, maximum throughput when processors can keep up&lt;/li&gt;
&lt;li&gt;Con: Memory grows unbounded, possible OOM, cascading failures&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Stop on any slowdown&lt;/strong&gt;: If any processor signals it’s overwhelmed, stop polling.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pro: Safe, no resource exhaustion&lt;/li&gt;
&lt;li&gt;Con: Slowest processor determines overall throughput&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Force synchronised pace&lt;/strong&gt;: All processors must process each batch before the next is fetched.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pro: All processors stay in sync, predictable memory usage&lt;/li&gt;
&lt;li&gt;Con: The Slowest processor becomes the bottleneck for all&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Slow down ingress&lt;/strong&gt;: Adaptively reduce polling rate based on processor feedback.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pro: Balances throughput and stability&lt;/li&gt;
&lt;li&gt;Con: More complex, needs tuning&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. Rolling buffer&lt;/strong&gt;: Buffer messages up to a limit, retry delivery to slow processors.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pro: Absorbs temporary slowdowns, maximises throughput&lt;/li&gt;
&lt;li&gt;Con: Needs memory limits, complex failure handling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Different systems need different strategies. Real-time dashboards might use strategy 1 (drop messages rather than lag). Financial transactions might use strategy 3 (consistency over throughput). Event forwarding to Kafka might use strategy 5 (buffer temporary network issues).&lt;/p&gt;
&lt;p&gt;I’m leaning toward making this configurable per consumer, with sensible defaults. The default would be a bounded buffer with adaptive polling slowdown.&lt;/p&gt;
&lt;h2 id=&quot;scaling-current-state-and-future-plans&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#scaling-current-state-and-future-plans&quot; aria-label=&quot;scaling current state and future plans permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Scaling: Current State and Future Plans&lt;/h2&gt;
&lt;p&gt;For now, the big benefit of having dumb consumers is that you can scale them horizontally. Of course, this works for offset-based solutions like event stores and streaming tools like Kafka. It may not always work for systems that remove the message once it’s handled. Still, current consumers are using only event stores as sources; Kafka will likely come next.&lt;/p&gt;
&lt;p&gt;You can group processors into consumers by that, reducing the number of polling jobs (one consumer polls/subscribes to one source).&lt;/p&gt;
&lt;p&gt;I already mentioned batching, which should also increase the throughput.&lt;/p&gt;
&lt;p&gt;Running multiple instances of the same processor causes conflicts. Both process the same events, update the same read models, and corrupt the state. &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; already has the basic capability to do &lt;a href=&quot;/en/distributed-locking-a-practical-guide/&quot;&gt;distributed locking&lt;/a&gt;, but it’s not fully plugged yet. This will come in future releases.&lt;/p&gt;
&lt;p&gt;For now, checkpointing can detect whether a newer checkpoint is already stored (which can suggest another processor is running) and stop processing.&lt;/p&gt;
&lt;p&gt;The recommended approach is to run the consumer as a separate service from the API. Then you can scale it separately. You can also set replicas=1 for the specific consumer to ensure one instance.&lt;/p&gt;
&lt;h2 id=&quot;rebuilding-projections&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#rebuilding-projections&quot; aria-label=&quot;rebuilding projections permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Rebuilding Projections&lt;/h2&gt;
&lt;p&gt;Event Sourcing enables rebuilding read models from events. Bug in projection? Fix code, rebuild. New read model? Populate from history.&lt;/p&gt;
&lt;p&gt;With processor-owned checkpoints, you can either rebuild read model from scratch by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stopping the processor.&lt;/li&gt;
&lt;li&gt;Delete read model data.&lt;/li&gt;
&lt;li&gt;Reset the checkpoint to the beginning.&lt;/li&gt;
&lt;li&gt;Restart processing.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Or doing blue greeen by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creating a new version of your storage (with Pongo, it’s just adding a suffix or prefix to your collection name).&lt;/li&gt;
&lt;li&gt;Start consumer since the beginning.&lt;/li&gt;
&lt;li&gt;Check if read models are close enough, and stopthe  old processor&lt;/li&gt;
&lt;li&gt;Start processing.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; you have even some syntactic sugar on top of consumers and processors to make this easier:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; rebuildPostgreSQLProjections &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett-postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rebuilder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rebuildPostgreSQLProjections&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  projection&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; shoppingCartsSummaryProjectionV2
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; rebuilder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will spin up a new consumer; other consumers and processors continue normally, with their checkpoints unaffected. You can specify the position from which you want to start, and also whether to truncate the end storage.&lt;/p&gt;
&lt;p&gt;We’ll need more metrics like gap detection and distributed locking to make it more plug-and-play.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#wrapping-up&quot; aria-label=&quot;wrapping up permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;The consumer/processor architecture in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; is about making event processing concerns explicit and separable:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consumers&lt;/strong&gt; handle delivery - getting events from sources to processors. They’re simple by design. When delivery breaks, you know where to look.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Processors&lt;/strong&gt; handle processing - doing useful things with events. They own their checkpoints, track their own progress, and handle their own failures.&lt;/p&gt;
&lt;p&gt;This separation gives you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flexibility to mix and match consumers and processors,&lt;/li&gt;
&lt;li&gt;Independent scaling of different processing workloads,&lt;/li&gt;
&lt;li&gt;Isolated failure domains,&lt;/li&gt;
&lt;li&gt;Easy projection rebuilds,&lt;/li&gt;
&lt;li&gt;Testability at multiple levels.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The design makes tradeoffs explicit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Partial progress over all-or-nothing (configurable soon),&lt;/li&gt;
&lt;li&gt;Processor-owned checkpoints over global tracking,&lt;/li&gt;
&lt;li&gt;Simplicity in consumers, complexity in processors,&lt;/li&gt;
&lt;li&gt;Eventual consistency for async operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s more to build - distributed locking, partitioning, better backpressure handling. There’s still a lot to do, but I believe the foundation is there, and I know real applications are using it already.&lt;/p&gt;
&lt;p&gt;I hope that this is a good food for thought, even if you’re not using &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;. I’m curious about your thoughts and feedback. I’ll try to tackle those cases in more detail in dedicated articles.&lt;/p&gt;
&lt;p&gt;If you have questions, feedback, or would like to help me speed up the planned stuff, come chat in the &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Emmett Discord&lt;/a&gt;. We have a small, but welcoming and awesome community.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Requeuing Roulette in Event-Driven Architecture and Messaging]]></title><description><![CDATA[I’m always saying that there’s a thin line between good and bad practice, and this thin line is named “Context”. That’s also true for the…]]></description><link>https://event-driven.io/en/requeuing_roulette_in_messaging/</link><guid isPermaLink="false">https://event-driven.io/en/requeuing_roulette_in_messaging/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7523e8bd658f1926a8c638e369ddc6fe/b09c1/2025-11-17-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAACk0lEQVR42m1TS08TURSe3+LOX+HejQuX6sq4J5EQw0qjiRuNEd3gRkk0qYmPApFKUbAoFKVOIcEWS6dMobXz6DzuTNt5z+e9M7QicJKTuXPPPd/9znfO5XDC4jgerYMggOM4sG0bsiyPXFVV9Pt9eJ6HKIr+y+NwhoVhiF6vB8MwoOt6Asj2mDEA13WTfQYuSVISH4KeAmTBarWKfD4PnuchiiIqlQrq9TqKxWISO86IXaQoSgI+YjgMat1uEvR9H4IgIJvNIpPJYHZ2FrlcLgFmzIY5Q09yNY169x9DplWH0j9SEjEtjelkGDpM04RlWYmmZ2seI6BMW20J3OgGk0BuiLQTIVRNx7f1DawXv2Nzs4RyeQuF1a/4QdelUinRkTWEyeO7BFFgIvJkmM0VcGEYIQ4dtGs/odW2ECr76KkiOns8Oq0a5I4IVT5Au92ALDVph1OtCDGprgICn7A/RI4A69czcH5Ay/ObMN6Mw16egpG7DWftEYzMTfhCnh5WaUUC/e5TFyi7/lG3067r9EJC2BgR+D0VHF8uY2ATCIsLOPy8BHFzDVKFx+6HBSjrH+EdFEAoQ7OrwFAleHRkjgOW8jOYe3UDLWEB7t4hLdlz8KflY3tDxU5Zw34jxl41QK1OORUKaL4Yg1BWsLsTYXfbhU3SxkRHcyluzKPy+jqs9hdatg9u6fFDrL19juy9ccyMXcPi9BSI5sLUfZiSCqP5G0QfgBgB3fNoM1Igi2rYaLbgqjVYc3cQqhUEkQnu6flzeHnxAlYnryB/fxLvJi5jQPRTz/CkOYMBDGLD0QR0P03B7VYxcGRw769ewsrELfDTD7D8ZALz03fTjChmiCloOmqpH5s/Nj4h1ZIdZZqy2f0LO9PCHiWwHXMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/7523e8bd658f1926a8c638e369ddc6fe/507e6/2025-11-17-cover.webp 200w,
/static/7523e8bd658f1926a8c638e369ddc6fe/28a80/2025-11-17-cover.webp 400w,
/static/7523e8bd658f1926a8c638e369ddc6fe/0367d/2025-11-17-cover.webp 626w&quot;
              sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/7523e8bd658f1926a8c638e369ddc6fe/36ca5/2025-11-17-cover.png 200w,
/static/7523e8bd658f1926a8c638e369ddc6fe/a3397/2025-11-17-cover.png 400w,
/static/7523e8bd658f1926a8c638e369ddc6fe/b09c1/2025-11-17-cover.png 626w&quot;
            sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/7523e8bd658f1926a8c638e369ddc6fe/b09c1/2025-11-17-cover.png&quot;
            alt=&quot;cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I’m always saying that there’s a thin line between good and bad practice, and this thin line is named “Context”.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s also true for the (anti-)pattern I’m calling &lt;em&gt;“Requeuing Roulette”&lt;/em&gt;&lt;/strong&gt;. Let’s discuss it today, continuing the “race condition series”:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;Dealing with Race Conditions in Event-Driven Architecture with Read Models&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/strict_ordering_in_event_handling/&quot;&gt;Handling Events Coming in an Unknown Order&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What’s the Requeuing Roulette? As the name suggests, this &lt;em&gt;technique&lt;/em&gt; involves putting a message back into the queue. It also (correctly) suggests that we’re hoping for the best. And sometimes we may be lucky to be true.&lt;/p&gt;
&lt;p&gt;The basic primitive for a messaging system is a queue. The producer is putting messages into the queue, and the consumer is getting them on the other end. If everything goes well, the consumer receives them in the order the producer put them (thus, a queue, like a queue in a shop).&lt;/p&gt;
&lt;p&gt;If the consumer is not available, the messaging system will try to deliver messages and handle retries for us.&lt;/p&gt;
&lt;p&gt;We discussed it in detail in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;Queuing, Backpressure, Single Writer and other useful patterns for managing concurrency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/the-order-of-things-why-you-cant/&quot;&gt;Ordering, Grouping and Consistency in Messaging systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/the-order-of-things-why-you-cant/&quot;&gt;The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/dealing-with-eventual-consistency/&quot;&gt;Dealing with Eventual Consistency, and Causal Consistency using Predictable Identifiers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ordering of processing works if we have a single consumer for a single queue. If we have more than one consumer, we lose the ordering guarantee. Why would we want to have more than one consumer? Obviously, to speed up processing. If messages in the queue are not causally correlated, then we can process them in parallel.&lt;/p&gt;
&lt;p&gt;What does the smartass “causally correlated” even mean? For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;depositing money into a bank account is causally correlated to opening it, as we can’t deposit money if we don’t open it.&lt;/li&gt;
&lt;li&gt;depositing money into a bank account is NOT causally correlated to other deposits, as we can deposit as much money as we have (of course, ignoring weird regulations),&lt;/li&gt;
&lt;li&gt;money withdrawal is causally correlated to depositing money and other withdrawals, as we need to check the balance, and they may impact it.&lt;/li&gt;
&lt;li&gt;Withdrawals and deposits are only causally correlated if they happen on the same bank account; other bank account operations can happen at any time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You get the idea, aye?&lt;/p&gt;
&lt;p&gt;So if we set up a queue to process money transfer events, then it could look as follows:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5cfc485c64729c5d89659db7932c40b1/1ce36/queue1.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABh0lEQVR42q2SXUsbQRSG8+vqlT+hvdNrhYJ/oi1IQRC/kGKVhloovciFURHcGFyrdbW9ELu7cWN0zW6S/YrZzO7T3YkpS++EDswceM7MO+c9MyX+8yjlS5qmJEkigfBshGMiHgMc16XTcWX+WYKZ5F/gV99hLb+ip5+hN5qYpvF8Qcdpo2marMaoLHC98RrzXMEwb/j185L2gz26Ns1cyJk+xWRUzJiNBaMowrZtwjAkznKDoaDnB9y7PZotGz/qSw8ib00hyllguZNS3rtut4vb6TLs+/i1dYLTLwhdoXewSGJ9xz/+iKeWGfxWcKvviY06Xn0DX/3E4LqGszNP3zxlmFcohKCmKOzuHyACB2vpJa3yHIGygj4/SaRu0lib4m5rFl9Z5erNBKG6hbk2TWtzhqD+Af3tC6LzryPBOI5xs9dsOw6PoUdL/Ub7Yg/POMM62sZrXNI8qXCv7eKZP7AOyxm7kEzua2jcHn2mf3eV2X7qYW57/HWKvUkLUeb/ZfJsgaUpfwA7WqQjRNe4WQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5cfc485c64729c5d89659db7932c40b1/507e6/queue1.webp 200w,
/static/5cfc485c64729c5d89659db7932c40b1/28a80/queue1.webp 400w,
/static/5cfc485c64729c5d89659db7932c40b1/8d2ea/queue1.webp 800w,
/static/5cfc485c64729c5d89659db7932c40b1/f478b/queue1.webp 966w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5cfc485c64729c5d89659db7932c40b1/36ca5/queue1.png 200w,
/static/5cfc485c64729c5d89659db7932c40b1/a3397/queue1.png 400w,
/static/5cfc485c64729c5d89659db7932c40b1/a331c/queue1.png 800w,
/static/5cfc485c64729c5d89659db7932c40b1/1ce36/queue1.png 966w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5cfc485c64729c5d89659db7932c40b1/a331c/queue1.png&quot;
            alt=&quot;queue1&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I assumed that we’re following the advice from the &lt;a href=&quot;/en/strict_ordering_in_event_handling/&quot;&gt;previous article&lt;/a&gt;. Besides the event type and payload, we’d also pass the record revision, which represents the logical order of events. It comes from the number incremented with each change. Assuming we’re publishing events after each successful business logic handling, it should be gapless.&lt;/p&gt;
&lt;p&gt;You may notice that our queue actually has multiple timelines for each causally correlated message sequence. If we simplify our considerations and assume that all events from a certain account are causally correlated, then we could visualise them as:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6af9167e8b2edcb44058795d401b5833/1ce36/queue2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByklEQVR42n2S3WvTUBjG+z+KF17qvZcieKc36o1sDJF5tQtBNjoUW7pCYUUsQimjc2lLkuZr3ercR7M2SZsmJ8nPk5TBBs4DT07y5Dk/zvueU+IfI8uyYh6Px7TbbbrdLoqi0Ol00HWd/43SfbAwDGnuN6lUKtRqNarVKo1Gg2qlyuRqcidLdguYm0II4jhGSCXxglSExMs51lBFHSgMpHrKr+K9r3RZBB5JIvNiWShfI+KQNE0p5Y8oigp6tJgwa77Drb9G9Lfx9l6Ral/y4qX+kP3+jl97yVL5TBabpIlN7NS52n1OeFRGpNlqh77vcT31ZMhlXH6G/ekpy4MPaO8f4f5Yl7DjArrUy6hrD3Bba/L7ROqMyNjF2HjI9Ocm6U0P86bv1et40ymngx4jWd6FZWB3DzjTVQKrRTDcx3U0nMNDLgydwGzh67mnF7k8L5JkVbLrusxm17LZIeYwQ/7D0FMsC2wt4nLnBZdbjxkdnWDa4EjvXHrnW0+kd4opcyM7Kc7hTg+nbogxCLDUeSFTXWCrPv2vm/S232DnQC3mWPNRv32kv/MWR5FANWJkzVfAm1NO5HaFSCT8HsWpXJDrtr/yVnNSXKO/YIGW2QeQh5oAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/6af9167e8b2edcb44058795d401b5833/507e6/queue2.webp 200w,
/static/6af9167e8b2edcb44058795d401b5833/28a80/queue2.webp 400w,
/static/6af9167e8b2edcb44058795d401b5833/8d2ea/queue2.webp 800w,
/static/6af9167e8b2edcb44058795d401b5833/f478b/queue2.webp 966w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/6af9167e8b2edcb44058795d401b5833/36ca5/queue2.png 200w,
/static/6af9167e8b2edcb44058795d401b5833/a3397/queue2.png 400w,
/static/6af9167e8b2edcb44058795d401b5833/a331c/queue2.png 800w,
/static/6af9167e8b2edcb44058795d401b5833/1ce36/queue2.png 966w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/6af9167e8b2edcb44058795d401b5833/a331c/queue2.png&quot;
            alt=&quot;queue2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s fine&lt;/strong&gt; to process events that are &lt;strong&gt;not&lt;/strong&gt; causally correlated, as by that we’re increasing throughput, not trading off correctness:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/618952684101989d1157806f1f8a1805/499a5/queue3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABuUlEQVR42p2SXW8SQRiF+b299A947Z2XeqcxNtaSWDW0KbGFYDSLxdAGQgsUuss3LLiwArs7H/s4u7S98LKTnHdm3jlz5p0zk4njmKT1HJti4ZxisYBlWZydfePiV5nvpRKu66acB+7DONYapQRKS9OHJqfIaKVSQrlSJXuU4yh3yvv9Dxyf5Ml+OWb/4JB6vZFytDYi95q9Xp/huE/QyOJbr4hFCyU9Msl6OGvhXX5mVv3KwmC1tFnPW8ysA5aDKtvtCCUmSOGaSmQq6Ps+oQz4W/vI6udrkJ2d4Hy5xm+cMDp8hnf+Ajf33NCnKL/K4N0eYT9v5g6Ia2KzScrwvlrNYjFnPBmn8DbmUC3I1Oo13PES+3pKrz2n35rh/XjD/Pcpdttj2P3DyPEZ2j6TwRoRiUcfr64uuahUsCplpkE7cZZMEiYjQbel6bY1zq2i8+klTiHH3R10m2atKencSJyOJIp2V46iCCE0wSpEeD6eKwgCTUaZR5FSGpheyBRBKMwGtcv9h+Rhdr+iS/N2glMqMHi7x01lYizQSYVPa8F2w2YrGbVsbCvPyFiyWaunC6b/MEHqHI/xH18Uow1yUSq6AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/618952684101989d1157806f1f8a1805/507e6/queue3.webp 200w,
/static/618952684101989d1157806f1f8a1805/28a80/queue3.webp 400w,
/static/618952684101989d1157806f1f8a1805/8d2ea/queue3.webp 800w,
/static/618952684101989d1157806f1f8a1805/68fc1/queue3.webp 1200w,
/static/618952684101989d1157806f1f8a1805/d6f8f/queue3.webp 1423w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/618952684101989d1157806f1f8a1805/36ca5/queue3.png 200w,
/static/618952684101989d1157806f1f8a1805/a3397/queue3.png 400w,
/static/618952684101989d1157806f1f8a1805/a331c/queue3.png 800w,
/static/618952684101989d1157806f1f8a1805/8537d/queue3.png 1200w,
/static/618952684101989d1157806f1f8a1805/499a5/queue3.png 1423w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/618952684101989d1157806f1f8a1805/a331c/queue3.png&quot;
            alt=&quot;queue3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;But &lt;strong&gt;it’s not fine&lt;/strong&gt; to process messages from the same timeline in parallel, as we may get race conditions when the consumer processing the earlier message will be slower than the one processing the later message.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/aa7870589930c2228d57677cd0d03c59/a88fd/queue4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 40%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABiUlEQVR42pWSXYvTQBRA8/f8Sf6LfRBfFMQFcdGlLwtLVXDFrn0I7cIu2N02tSvpR5yktR9pmyYzk7M3EwUfRHDgcMnk3pM7N+MhqyzLKjAMAi5bLXzfd7TbbTqdDmEY8mdeFSuM0b8oMLrAWoNnrZUN4xJ7d30aZ+e8PW3QbL7nVGLFZDJ1762thWmaMhqNMEUsT0u2V69YfTrCktfCyry9brC5OiFpPWeb3JBlIft0yE4weow1Y4kLqibzPGe9XpFnSxHmpKHP8u6DeDK8KIpQKiK5fEHy+Rnxx6fYVZeyCITvDnMYobOA4qCkyJ3Zddq9OKbbeUk6u6fMCrfnKaWYTSPuv0FwqwkkjpvHTM+eMBzIXG8LQTP4qlGzqshSOiu0Tx7z5fUj9lEPvdnVwnqGlkWcM1cHFnPLj0EP1b9hkRjmcVGjCjbruov9bodKfpJPfA7Xb7AyCmvm6CLF+/3H/mdVwiies+pfMH13BFpGIRgtwr8VuA+U9p9Sd23kdFryrFwdh7U8AGynWqu3kBhzAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/aa7870589930c2228d57677cd0d03c59/507e6/queue4.webp 200w,
/static/aa7870589930c2228d57677cd0d03c59/28a80/queue4.webp 400w,
/static/aa7870589930c2228d57677cd0d03c59/8d2ea/queue4.webp 800w,
/static/aa7870589930c2228d57677cd0d03c59/68fc1/queue4.webp 1200w,
/static/aa7870589930c2228d57677cd0d03c59/14fa8/queue4.webp 1505w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/aa7870589930c2228d57677cd0d03c59/36ca5/queue4.png 200w,
/static/aa7870589930c2228d57677cd0d03c59/a3397/queue4.png 400w,
/static/aa7870589930c2228d57677cd0d03c59/a331c/queue4.png 800w,
/static/aa7870589930c2228d57677cd0d03c59/8537d/queue4.png 1200w,
/static/aa7870589930c2228d57677cd0d03c59/a88fd/queue4.png 1505w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/aa7870589930c2228d57677cd0d03c59/a331c/queue4.png&quot;
            alt=&quot;queue4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We actually have two orders in messaging systems:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Queue Order:&lt;/strong&gt; The order messages are produced to the queue&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Processing Order:&lt;/strong&gt; The order messages are actually consumed and processed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We already learned that we can detect the out-of-order processing by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;business rules or&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/strict_ordering_in_event_handling/&quot;&gt;detecting the gap between the last processed revision and the event revision&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e58596f6c0825885c45299432e65a67b/74506/queue5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 30.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABVklEQVR42o2R207jQBBE/cf7HWg/Yd8RYrMSLyCREGCBgIggQuGBGLIRm9jmEjvG10k8l8NMEDxTUkujVk11dbVnjMGhKCuOTnq0O12Ojv/SOTjE9+/5hNaasiyRskKtMlQeUOWCLDPULzF6tVzzPKdnZE01u+G6+5t+b5/h7SUXvTbP0diNwhhJ00iiMEAjUNk/4u4GV8dTdls1z35uRRTOmpdmBXHgk3R+kp//QiZ92w5tRWj5iBI+ShZfTof9HaaTM5q3FG0HOdzGf3iMB5TWsbdIE8IgYeIveXiA//ubTE87jO17MqoZ3y0pcvfxI5rzrR+MzzYwtaaRat27n3eZRDckcYrniFLafHJFVdkF56+UycLmZSgLtRZTynKaFeHLnGLQQox2iKKEve03hoPUSto4gidms5kT/B7cUYQQ1GnIKo8QdcnitSG2qwtlj6S0NSZ5B2XuwdVvgS+zAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/e58596f6c0825885c45299432e65a67b/507e6/queue5.webp 200w,
/static/e58596f6c0825885c45299432e65a67b/28a80/queue5.webp 400w,
/static/e58596f6c0825885c45299432e65a67b/8d2ea/queue5.webp 800w,
/static/e58596f6c0825885c45299432e65a67b/68fc1/queue5.webp 1200w,
/static/e58596f6c0825885c45299432e65a67b/057fc/queue5.webp 1432w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/e58596f6c0825885c45299432e65a67b/36ca5/queue5.png 200w,
/static/e58596f6c0825885c45299432e65a67b/a3397/queue5.png 400w,
/static/e58596f6c0825885c45299432e65a67b/a331c/queue5.png 800w,
/static/e58596f6c0825885c45299432e65a67b/8537d/queue5.png 1200w,
/static/e58596f6c0825885c45299432e65a67b/74506/queue5.png 1432w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/e58596f6c0825885c45299432e65a67b/a331c/queue5.png&quot;
            alt=&quot;queue5&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We can also apply techniques like the &lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;&lt;em&gt;Phantom record&lt;/em&gt;&lt;/a&gt;, where we keep data as it comes and take the next steps only when defined conditions are met. And that’s also what I’d recommend in general, but…&lt;/p&gt;
&lt;p&gt;But I promised you to talk today about the &lt;em&gt;Requeuing Roulette&lt;/em&gt; (anti)pattern, aye? So let’s do it!&lt;/p&gt;
&lt;p&gt;If we’re using tools like RabbitMQ, SQS and other classical messaging tooling (so not you Kafka! You’re a streaming or log solution!), then we can put the message back in the queue.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.rabbitmq.com/docs/semantics#ordering&quot;&gt;RabbitMQ message ordering documentation states&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Messages published in one channel, passing through one exchange and one queue and one outgoing channel will be received in the same order that they were sent. RabbitMQ offers stronger guarantees since release 2.7.0.&lt;/p&gt;
&lt;p&gt;Messages can be returned to the queue using AMQP methods that feature a requeue parameter (basic.recover, basic.reject and basic.nack), or due to a channel closing while holding unacknowledged messages. Any of these scenarios caused messages to be requeued at the back of the queue for RabbitMQ releases earlier than 2.7.0. From RabbitMQ release 2.7.0, messages are always held in the queue in publication order, even in the presence of requeueing or channel closure.&lt;/p&gt;
&lt;p&gt;With release 2.7.0 and later it is still possible for individual consumers to observe messages out of order if the queue has multiple subscribers. This is due to the actions of other subscribers who may requeue messages. From the perspective of the queue the messages are always held in the publication order.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The last paragraph seems promising, as it suggests the message will be put back before the next messages, since it was placed in the queue.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d4eaac01d4cccdb434695c1d0d8cd093/a60fa/queue6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 36.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABdUlEQVR42l2R3W7TQBCF/cY8AG/BNTdcIm6QQEICIdWqKFSqUUzcEiqR2sR/TWwnXv9k1/b6Y520KWKk0ezMGZ05M2vxYIvFAsdxmM1muK6L53k0TUOe5wghqKrqEKd8qk82jkf/16wjMKKUIgxDbNtmuVyy2WwMQWkwfcAf+7QeDOmWvVQnknEYnginpvG/MW3bkhfbE8lkTdOyvk/MSyALlzI4Z/5dcu20tKupfuyzBhM7kVBdvqRxXoEKaQKb3Y/XBo7RvW9IFV2vqYVR1mao8hdVdMX1THLjCgoRMejeqNdYa7Paavmb4KvN3eUFW/ct6dUHwvmcYr0mS1KUlIfpZZbw6c0zktszdNMfaqKLOA9ekNyHZrM9llKSspRE0UicGk3fPhJ7P4liCO96Qr+nU9oo7MmzlD/vn7OPvxAFEs/ZU4mOssrwfZ+6rp8+BfSDT9d4zMfTbaa7bncFlfsOmXnc3uy4+FyzShP02JMYNVJK/gKT0RF8/YyJvAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d4eaac01d4cccdb434695c1d0d8cd093/507e6/queue6.webp 200w,
/static/d4eaac01d4cccdb434695c1d0d8cd093/28a80/queue6.webp 400w,
/static/d4eaac01d4cccdb434695c1d0d8cd093/8d2ea/queue6.webp 800w,
/static/d4eaac01d4cccdb434695c1d0d8cd093/68fc1/queue6.webp 1200w,
/static/d4eaac01d4cccdb434695c1d0d8cd093/241b8/queue6.webp 1467w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d4eaac01d4cccdb434695c1d0d8cd093/36ca5/queue6.png 200w,
/static/d4eaac01d4cccdb434695c1d0d8cd093/a3397/queue6.png 400w,
/static/d4eaac01d4cccdb434695c1d0d8cd093/a331c/queue6.png 800w,
/static/d4eaac01d4cccdb434695c1d0d8cd093/8537d/queue6.png 1200w,
/static/d4eaac01d4cccdb434695c1d0d8cd093/a60fa/queue6.png 1467w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d4eaac01d4cccdb434695c1d0d8cd093/a331c/queue6.png&quot;
            alt=&quot;queue6&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, it’s only a best effort. &lt;a href=&quot;https://www.rabbitmq.com/docs/nack&quot;&gt;Another place in the documentation states&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When a message is requeued, it will be placed to its original position in its queue, if possible. If not (due to concurrent deliveries and acknowledgements from other consumers when multiple consumers share a queue), the message will be requeued to a position closer to queue head.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So in the worst case, it can even end up like that:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f5d4e42b55fdfe2071df4c27151d7766/a60fa/queue7.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABj0lEQVR42n2RW2/TQBBG/Yv5LQjxwAMPCCHxzBNCKiUKlwYLYaBKmsYNblMndl3fb2uvL4eN6xSEBCN90qx298zMNxp/hZSSJElI05SqqhBCkOc5RVEQhiFxHNO2Lf8K7c9DqT45jsNkMhk0nU6ZzWbYtj3c931PXdf4vk/TNv8HNk1DEEZDHgQhhvFNyeDy8mroqB+B+yjKkkRNMFahk/Vv4OFRGIWkWxuRZZwtzzg/X2KaJov5HMuyhoKOs1PwmL5cEZmv+TyNMU5SxNYd4Qooa0nd1sT6C6KjR2RfnhG8eUiVr5WfP2nEkkYmdN3ekowosMjDNYU7Z7OW2Fc1brYgL2Nlh0SLo4iNvWWjf8c8/sjaWGAevSf48BzfusCxE8q8vGtAQd++fMBKfwr53WLarkZ3n7C+PiWOMrSua6lEg3fb4/lKHtz6He7Xd9xYHmpKRNmqkWu8IOJm8ph89YqtnXJynLC7Fqp7Ofi7l3ZIoBt1yEdT6O+XlhUl6cUnpHeK5yb80AWbnUeLuF/KLz15Xe44GTQuAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/f5d4e42b55fdfe2071df4c27151d7766/507e6/queue7.webp 200w,
/static/f5d4e42b55fdfe2071df4c27151d7766/28a80/queue7.webp 400w,
/static/f5d4e42b55fdfe2071df4c27151d7766/8d2ea/queue7.webp 800w,
/static/f5d4e42b55fdfe2071df4c27151d7766/68fc1/queue7.webp 1200w,
/static/f5d4e42b55fdfe2071df4c27151d7766/241b8/queue7.webp 1467w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/f5d4e42b55fdfe2071df4c27151d7766/36ca5/queue7.png 200w,
/static/f5d4e42b55fdfe2071df4c27151d7766/a3397/queue7.png 400w,
/static/f5d4e42b55fdfe2071df4c27151d7766/a331c/queue7.png 800w,
/static/f5d4e42b55fdfe2071df4c27151d7766/8537d/queue7.png 1200w,
/static/f5d4e42b55fdfe2071df4c27151d7766/a60fa/queue7.png 1467w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/f5d4e42b55fdfe2071df4c27151d7766/a331c/queue7.png&quot;
            alt=&quot;queue5&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Then the fun will begin. Now we have two messages that we already see will be handled out of order, and we’ll need to requeue them, hoping they land after the message we need to process as the next one. What if we have multiple messages like that? What if our consumer randomly fails when processing our message with revision 12, and we need to requeue it again? How lucky will it be with Requeuing Roulette?&lt;/p&gt;
&lt;p&gt;As you can see, the more correlated our messages are and the more we’d like to parallelise them, the more likely we are to face Requeuing Roulette. Typically, we put messages into the same queue so we can process them in order.&lt;/p&gt;
&lt;h2 id=&quot;when-requeuing-roulette-is-helpful&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#when-requeuing-roulette-is-helpful&quot; aria-label=&quot;when requeuing roulette is helpful permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;When Requeuing Roulette is helpful?&lt;/h2&gt;
&lt;p&gt;It can be useful if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We want to get the best parallelism for message processing, and ordering best effort is good enough for us.&lt;/li&gt;
&lt;li&gt;Our messages are most of the time not causally correlated or…&lt;/li&gt;
&lt;li&gt;Events for the same records/processes are not quickly published one after another, so we could safely retry message before the next one will arrive,&lt;/li&gt;
&lt;li&gt;Our consumers are stable, not failing too often.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you see, those assumptions can be fragile and classical &lt;em&gt;famous last words&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Of course, we can use one of the techniques like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RabbitMQ routing key, correlation id,&lt;/li&gt;
&lt;li&gt;AWS SQS message group id, visibility timeout,&lt;/li&gt;
&lt;li&gt;Azure Service Bus sessions,&lt;/li&gt;
&lt;li&gt;etc. see &lt;a href=&quot;/en/the-order-of-things-why-you-cant/&quot;&gt;Ordering, Grouping and Consistency in Messaging systems&lt;/a&gt; for details.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Making this trade-off more in favour of the ordering guarantee or parallelism may reduce it to an acceptable level, but you need to be aware of the risk of an unexpected traffic spike or a different message distribution than you expected.&lt;/p&gt;
&lt;h2 id=&quot;dangers-of-requeuing-roulette&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dangers-of-requeuing-roulette&quot; aria-label=&quot;dangers of requeuing roulette permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dangers of Requeuing Roulette&lt;/h2&gt;
&lt;p&gt;Even when order doesn’t matter, requeueing has a hidden cost that becomes visible under load.&lt;/p&gt;
&lt;p&gt;Suppose you reject the message with requeue set to true. In that case, it can be redelivered to your consumer almost instantly, resulting in a very high workload, since your consumer will reject it again.&lt;/p&gt;
&lt;p&gt;Let’s say you have a message that fails because a downstream service is down. You requeue it. It immediately returns to the consumer (maybe even the same one), fails again, and is requeued. This can happen hundreds of times per second. It can also swamp the slow consumer, preventing it from even recovering.&lt;/p&gt;
&lt;p&gt;In the worst case, your CPU can be spent processing and requeueing the same 10 messages over and over, while thousands of processable messages sit behind them in the queue (because RabbitMQ will try to put requeued messages before the next messages).&lt;/p&gt;
&lt;h2 id=&quot;what-about-kafka&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-about-kafka&quot; aria-label=&quot;what about kafka permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What about Kafka?&lt;/h2&gt;
&lt;p&gt;Well, in Kafka, this issue doesn’t exist as Messages with the same record key go to the same partition, maintaining order within that partition while allowing parallel processing across partitions.&lt;/p&gt;
&lt;p&gt;So Kafka, for the win? Hold your horses!&lt;/p&gt;
&lt;p&gt;Only one consumer from the consumer group can handle a specific partition. So, within a single partition, parallelisation isn’t possible. If we map the RabbitMQ queue to Kafka’s partition, then the conclusion can be that Kafka solved it by removing this feature.&lt;/p&gt;
&lt;p&gt;Also, when we consume a message from the classical messaging system (like RabbitMQ), it will be removed from the queue. In a streaming solution like Kafka/Pulsar, etc., they will remain in the log until the &lt;a href=&quot;/en/gdpr_in_event_driven_architecture/#log-compaction&quot;&gt;retention policy kicks in and drops old messages from the partition&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Kafka maintains the offset of the last processed message in each topic partition. You don’t need to requeue messages; you can just rewind the offset to an older position when you want to reprocess messages.&lt;/p&gt;
&lt;p&gt;Read more in &lt;a href=&quot;/en/kafka-consumers-under-the-hood-of/&quot;&gt;Kafka Consumers: Under the Hood of Message Processing&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h2&gt;
&lt;p&gt;The “requeueing roulette” is a symptom of trying to solve a distributed systems problem with a technical solution.&lt;/p&gt;
&lt;p&gt;The requeueing roulette is seductive because it promises something impossible: maintaining strict order in a distributed, concurrent system without sacrificing throughput. It’s trying to cheat the fundamental trade-offs of distributed systems.&lt;/p&gt;
&lt;p&gt;Still, cheating can take us far enough, but there’s always a danger that we’ll be caught and handcuffed.&lt;/p&gt;
&lt;p&gt;If you’re considering using Requeuing Roulette, then consider the other techniques I described in previous articles. I’d treat Requeuing Roulette as a temporary solution and a tradeoff.&lt;/p&gt;
&lt;p&gt;In my opinion, if you decide to use it, then the question isn’t whether you’ll abandon it, but how much pain you’ll endure before you do.&lt;/p&gt;
&lt;p&gt;The real skill isn’t in making requeueing work - it’s in understanding your actual ordering requirements and choosing the most straightforward solution that meets them. Often, that means accepting that perfect ordering is neither necessary nor worth its cost, especially in the long term.&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;Dealing with Race Conditions in Event-Driven Architecture with Read Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/the-order-of-things-why-you-cant/&quot;&gt;The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/internal_external_events/&quot;&gt;Internal and external events, or how to design event-driven API&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/&quot;&gt;Dealing with Eventual Consistency and Idempotency in MongoDB projections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;Queuing, Backpressure, Single Writer and other useful patterns for managing concurrency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/the-order-of-things-why-you-cant/&quot;&gt;Ordering, Grouping and Consistency in Messaging systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/the-order-of-things-why-you-cant/&quot;&gt;The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/dealing-with-eventual-consistency/&quot;&gt;Dealing with Eventual Consistency, and Causal Consistency using Predictable Identifiers&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Handling Events Coming in an Unknown Order]]></title><description><![CDATA[After the last article on Dealing with Race Conditions in Event-Driven Architecture with Read Models, I got such a question from Ben: You…]]></description><link>https://event-driven.io/en/strict_ordering_in_event_handling/</link><guid isPermaLink="false">https://event-driven.io/en/strict_ordering_in_event_handling/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/64d3faf9c17c4be8610248e5e577e4bc/b09c1/2025-11-03-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADrklEQVR42g2S/VPTBQCHv6e8eGjGqQPB5NXBxjbcxmC8jBdxgINBgyED3AbJGIUtmW+ISjNeBMMLQaF4UwaRB4YhtsSdgXBq4nlEXt1Zv3TXT/VXPPEffJ7n+QgBu/YTvDeKHaI4AsIT2RGfTkhaJTvzHIRk1rBLXUpukRGbtYbB+nJ0Gi0GSQKm7BK2q0zsO5RGcLSGHREyAnaGIwSGRhG0X8Y+eR7pls8QG53EWbuJPT9HrHuSvu4exi+dxF1upMjmosNp5+XHBvrLsskprsRSVMD7WhsBh/QEiRIQgkSJ7JbpuTqxQPudRSxXbuPon6Gud4jBgVsMuerorcxnOEtGSW4hnV2d/HfFiL/TxmS7k+slahTZxxHkxwmKzkAQYo6QZnby6NlrXJ4B1JZPOHljiB5XBdbGZoo//5rlgQ5Wq7XUqmV8ZDbwuNlIp6WAiSYT02UqAmMzCBAXExiRsoVs6OPUrTm89/1MzT2hy7tA38g4loIMdEUlFJRW4r7g4UKBjiyVgr62Ri7WGnBnSnHokugpLaOi3IUgO7HV4DBC6NFW7ixvsv7X39y8v0LD9QVaeh5wrXeYwpwMtNIYyu1OjBYHZn0uM8fzOJclpU0vZ9SkwZyZx0V7C3ulx9gmUiCk1t/gwfNX+N/8jmd0jctfPcUz8DNXxtZwNDm4fsbBGUM6kbHxVGencSE1iYn8RH6zyVkwSWjQqum11RMqOcb2sK2F1Ze/YWn9DXPP1unxrjE284KRu6t8N++nvaWexqoPaclPQRcdScoHYURGiIgLF3FGGUW3KpauiirOWhrZJiklWJSEMPn9Kr71tyz+skH/nI/Vt3/ycvMd/keznGuqRJemJFsaT7E4CqdKjEElJTxsHyZZNOeVMqZNZSQXtRAiK936YjLC6a5JOsbu03p7irabE3ifvOCPf/5lZuQa5ToFR1US7GoJU0eS0WsUFCoTSTwYSVNqPM1KBUN6DVW1pxGln2B3lAphxLuMb2mFp2vL+B/Pc+/eXX58+AOt5jw+NWSSo5FjUYiZLVThri2hJjeFQwcP4NTG4UiW0K1O5JTFRkx+Le/FbDl8ODTOtx4Xs8NXudZ1CW9bPcOD/XR3tLPoW2DMbWcwN4Gls3ZezQwyfrYOq1yMRxPNCVksQ4eTuGmrIKa4gj0SJcLlBhs1ZhNFBflU65U4jOn85H/M841NNl4vszr6Bb6aNN6tzPPr5gbr3i8ZNOm4lyXGmnCAabUCn6MMs6sZrdnK//nqKi86rGgpAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/64d3faf9c17c4be8610248e5e577e4bc/507e6/2025-11-03-cover.webp 200w,
/static/64d3faf9c17c4be8610248e5e577e4bc/28a80/2025-11-03-cover.webp 400w,
/static/64d3faf9c17c4be8610248e5e577e4bc/0367d/2025-11-03-cover.webp 626w&quot;
              sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/64d3faf9c17c4be8610248e5e577e4bc/36ca5/2025-11-03-cover.png 200w,
/static/64d3faf9c17c4be8610248e5e577e4bc/a3397/2025-11-03-cover.png 400w,
/static/64d3faf9c17c4be8610248e5e577e4bc/b09c1/2025-11-03-cover.png 626w&quot;
            sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/64d3faf9c17c4be8610248e5e577e4bc/b09c1/2025-11-03-cover.png&quot;
            alt=&quot;2025 11 03 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;After the last article on &lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;Dealing with Race Conditions in Event-Driven Architecture with Read Models&lt;/a&gt;, I got such a &lt;a href=&quot;https://www.architecture-weekly.com/p/dealing-with-race-conditions-in-event/comment/171420356&quot;&gt;question from Ben&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You described the scenario where you know what events you should receive, just not the order. But what if you don’t know that? For example, you get an ItemRemovedFromCart event, but the item doesn’t exist in your view of the current state of the cart. Is it an invalid event? Or is there an ItemAddedToCart event that hasn’t come through yet?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s a good question, and good questions usually require more depth to give a precise answer. That’s what we’re here for!&lt;/p&gt;
&lt;p&gt;Let’s follow up and discuss how to determine whether we have complete information for our events!&lt;/p&gt;
&lt;h2 id=&quot;what-we-learned-so-far&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-we-learned-so-far&quot; aria-label=&quot;what we learned so far permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What we learned so far&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Communication in messaging systems works like a department store. It has multiple cash registers and separate queues for each of them.&lt;/strong&gt; You can only guess which person will be handled in a specific queue: first in, first out. Between queues, you only know that the slowest will be the one you’re standing in.&lt;/p&gt;
&lt;p&gt;Of course, you could put a single cash register and a single queue, and everything would be sequential. Such a setup can work for small groceries with few customers. Tho, for a supermarket, it’d end up with an extremely long waiting queue.&lt;/p&gt;
&lt;p&gt;You can think about a single module as a single cash register in a department store or a small grocery store. Inside it, you can get strict ordering of processing, but not in the relationship with the outside world. For instance, you may know that on Monday morning, you’re getting the fresh fruits delivery, and at noon, you’re getting the dairy delivery. And it’s typically like that, but from time to time, because of the fruit delivery delay, you may get your fresh dairy first.&lt;/p&gt;
&lt;p&gt;Is it an issue? Not a huge one, as you just want them to come asap so you have fresh stuff to sell. When would it be an issue? If you were running the Milk Shake Cafe and needed both to make your special strawberry shake recipe.&lt;/p&gt;
&lt;p&gt;In many systems, ordering is not a key concern, especially when we partition the workload. The issue may arise when we need to correlate separate actions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In the last article, we discussed the payment verification workflow.&lt;/strong&gt; To make the final decision, we needed to correlate data from the external payment gateway with our own modules, which calculate fraud scores, check limits, and assess risk. Only after receiving data on available merchant limits and the fraud assessment score could we make the final decision. Those pieces of information could return to us at different times and in a different order.&lt;/p&gt;
&lt;p&gt;To resolve it, we were just gathering and aggregating data as they went. Then, after each step, we checked whether we now have all the data. If we had, we were making the final decision; if not, we were storing it as is, assuming that at some point, data would arrive.&lt;/p&gt;
&lt;p&gt;If we model that as the workflow, then it’d look like that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerificationEvent
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PaymentVerification
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; VerificationEvent&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  current &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; current &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// (...) other event handlers&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;MerchantLimitsChecked&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        merchantLimits&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          withinLimits&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withinLimits&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          dailyRemaining&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dailyRemaining&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          checkedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        lastUpdated&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tryCompleteVerification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;FraudScoreCalculated&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculatedAt &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assessedAt
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        fraudAssessment&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          score&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;score&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          riskLevel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;riskLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          assessedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculatedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        lastUpdated&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculatedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tryCompleteVerification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tryCompleteVerifications&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerificationEvent
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PaymentVerification
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; VerificationEvent&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Ignore if we already made decision&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decision&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Check if we now have BOTH critical pieces&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;merchantLimits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Don’t have both yet - stay in processing&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;processing&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      dataQuality&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;processing&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; decision &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;riskLevel &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;high&apos;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;declined&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;High fraud risk&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;merchantLimits&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withinLimits
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;declined&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;High fraud risk&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;approved&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Verified&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; decision&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;approval&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      decision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;PaymentVerificationCompleted&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; decision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This works fine, as we know precisely which steps need to happen, so we know what we’re waiting for. And that’s how we made the full loop to Ben’s question. What if we didn’t know which steps we’re waiting for?&lt;/p&gt;
&lt;h2 id=&quot;how-to-know-what-we-dont-know&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-to-know-what-we-dont-know&quot; aria-label=&quot;how to know what we dont know permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How to know what we don’t know?&lt;/h2&gt;
&lt;p&gt;Let’s have a look at the case brought by Ben: the e-commerce flow. First, we complete the shopping cart by adding and removing items, then we confirm it. The example event flow could look as follows for the online food ordering:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
CartConfirmed       (cartId:1, confirmedAt: 2025-11-03 11:44:27)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We see here that someone added the first Pizza, then maybe accidentally added it again, corrected their mistake, and confirmed the order.&lt;/p&gt;
&lt;p&gt;Then, if that was an online ordering system and we had it integrated with the kitchen ordering, then we could get those events in a different order, for instance:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
CartConfirmed       (cartId: 1, confirmedAt: 2025-11-03 11:44:27)
ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We see that someone removed one Pizza from their shopping cart, which suggests that some information is missing. When we get a confirmation event, we still know that there’s more to come, as an order with a removed item doesn’t make sense. The same goes for the information that one pizza was added; when we correlate it with the removal event having the same cart identifier, we still see zero items in the shopping cart. Once we get the next event, we will finally know that we have more than one item in our shopping cart.&lt;/p&gt;
&lt;p&gt;Can we then proceed? Maybe yes and maybe no. For this particular order, it’d be correct, but what if our real order:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
ItemAddedToCart     (cartId: 1, name: Spaghetti Carbonara)
ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
CartConfirmed       (cartId: 1, confirmedAt: 2025-11-03 11:44:27)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Also, since messaging systems retry to ensure delivery, how would we know that those “doubled” events for adding or removing are actually distinct events and not just retries?&lt;/p&gt;
&lt;p&gt;For instance, in such a delivery case:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
CartConfirmed       (cartId: 1, confirmedAt: 2025-11-03 11:44:27)
ItemAddedToCart     (cartId: 1, name: Spaghetti Carbonara)
ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s discuss a few strategies to deal with that!&lt;/p&gt;
&lt;h2 id=&quot;external-vs-internal-events&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#external-vs-internal-events&quot; aria-label=&quot;external vs internal events permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;External vs Internal events&lt;/h2&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Doctor, it hurts when I bend my arm this way.&lt;/li&gt;
&lt;li&gt;Then don’t bend it this way&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 550px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/02ac7637b515b37e804b282785aaa420/021b5/2025-11-03-pun-small.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAIEAQX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAP/aAAwDAQACEAMQAAAB9C8+gc6jV0ign//EABsQAAMAAgMAAAAAAAAAAAAAAAABAgMREiEx/9oACAEBAAEFApVMabeSuNdy9DmTIP3SP//EABcRAAMBAAAAAAAAAAAAAAAAAAABEBH/2gAIAQMBAT8BMd//xAAWEQADAAAAAAAAAAAAAAAAAAAAEBH/2gAIAQIBAT8BK//EAB0QAAICAQUAAAAAAAAAAAAAAAARAQIhEFFhgZH/2gAIAQEABj8CiWjsRNa7jz6ZqRyhaf/EABsQAQEBAAMBAQAAAAAAAAAAAAERACExQVGB/9oACAEBAAE/IRIOTQ6uEcWYC5yTPjurrOgca9mBWEUl/c2PDreb/9oADAMBAAIAAwAAABC0z37/xAAYEQEAAwEAAAAAAAAAAAAAAAABABARIf/aAAgBAwEBPxB0xg3Stn//xAAYEQEAAwEAAAAAAAAAAAAAAAABABARIf/aAAgBAgEBPxAR5Ml//8QAHxABAAMAAgEFAAAAAAAAAAAAAQARITFBcWGBobHB/9oACAEBAAE/EE9U1qrJ7nmAGAC3Wdsv5nOsVtmzu3rng8Q4MsQaEItyuVv9ghAoc2g+pozgpfdxoL8mf//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/02ac7637b515b37e804b282785aaa420/507e6/2025-11-03-pun-small.webp 200w,
/static/02ac7637b515b37e804b282785aaa420/28a80/2025-11-03-pun-small.webp 400w,
/static/02ac7637b515b37e804b282785aaa420/33862/2025-11-03-pun-small.webp 550w&quot;
              sizes=&quot;(max-width: 550px) 100vw, 550px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/02ac7637b515b37e804b282785aaa420/37402/2025-11-03-pun-small.jpg 200w,
/static/02ac7637b515b37e804b282785aaa420/4cda9/2025-11-03-pun-small.jpg 400w,
/static/02ac7637b515b37e804b282785aaa420/021b5/2025-11-03-pun-small.jpg 550w&quot;
            sizes=&quot;(max-width: 550px) 100vw, 550px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/02ac7637b515b37e804b282785aaa420/021b5/2025-11-03-pun-small.jpg&quot;
            alt=&quot;doctor&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;One of the most common mistakes we learn too late is separating our events into internal and external (or private and public). &lt;a href=&quot;/en/events_should_be_as_small_as_possible/&quot;&gt;Internal information can and should be more granular&lt;/a&gt;. We need it to be precise in capturing the business context and making our decision.&lt;/p&gt;
&lt;p&gt;Yet, other parts of our system don’t need to know all of that. Is the kitchen interested in the details of all the changes procrastinating customer made to their shopping cart? No, they just want the final information on which meal they need to prepare.&lt;/p&gt;
&lt;p&gt;So in our example, if we published to the outside world just:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;CartConfirmed  {
    cartId: 1,
    items: [ { name: &quot;Spaghetti Carbonara&quot; } ],
    confirmedAt: &quot;2025-11-03 11:44:27&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Such a type of event is also called a &lt;strong&gt;Summary Event&lt;/strong&gt;. We should not mistake it with &lt;em&gt;the latest state&lt;/em&gt;. It’s still an event because it tells what has happened business-wise. It gathers all the information needed for other modules and summarises the changes. And no more than that. It should still be as small as possible and expose only the information that other modules need. It’s a contract made between different teams. I wrote about it in detail &lt;a href=&quot;/en/internal_external_events/&quot;&gt;Internal and external events, or how to design event-driven API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can define such an internal event API as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ItemAddedToCart&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:int:ItemAddedToCart&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    cartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ItemRemovedFromCart&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:int:ItemRemovedFromCart&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    cartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CartConfirmed&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:int:CartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    cartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    confirmedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartEvent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ItemAddedToCart
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ItemRemovedFromCart
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; CartConfirmed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and public as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CartOpened&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:ext:CartOpened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    cartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    openedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CartConfirmed&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:ext:CartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    cartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    productItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    confirmedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartExternalEvent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; CartOpened &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; CartConfirmed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you see, we can even have more than one summary event, and not even be one-to-one with an internal event. Maybe we also have an analytics module that analyses how long it takes the user to make a final decision after adding the first product. Then we may decide to expose such an event, hiding the details of the internal flow. We’re also defending ourselves and &lt;a href=&quot;/en/how_to_do_event_versioning/&quot;&gt;minimising the need for versioning when flow changes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ok, but how to map internal events into external?&lt;/p&gt;
&lt;p&gt;We can enrich them using such a function:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ShoppingCartExternalEvent &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./shoppingCart.external&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ShoppingCart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ShoppingCartEvent &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./shoppingCart.internal&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; enrich &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartEvent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartExternalEvent &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:int:ItemAddedToCart&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:ext:CartOpened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              cartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
              openedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:int:CartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:ext:CartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          cartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          productItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;productItems &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          confirmedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;confirmedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can then subscribe to internal events in our module, load the state (best to build it from events if we’re using Event Sourcing), and publish enriched events externally.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If we’re using messaging, this means also separating queues/topics.&lt;/strong&gt; If you’re using Kafka both for internal and external communication, then you should separate topics and have two different topics for outgoing communication, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;‘carts:events:int’&lt;/li&gt;
&lt;li&gt;‘carts:events:out’.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Similarly, for RabbitMQ or similar tools, you should have separate queues for internal and external communications.&lt;/p&gt;
&lt;p&gt;This is important, as you can now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Publish messages that other modules need, decreasing the number of issues with ordering,&lt;/li&gt;
&lt;li&gt;Have enrichment as an anti-corruption layer for your internal process changes,&lt;/li&gt;
&lt;li&gt;You can have different scaling capabilities for internal and external events. Maybe for internal, you don’t even need a messaging system, maybe &lt;a href=&quot;/en/outbox_inbox_patterns_and_delivery_guarantees_explained/&quot;&gt;outbox or event store subscriptions&lt;/a&gt; will be enough? Maybe you could cut costs using AWS SQS for internal communication and AWS Kinesis for cross-module?&lt;/li&gt;
&lt;li&gt;You can now define different security for those topics and retention policies.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sweet, right?&lt;/p&gt;
&lt;h2 id=&quot;its-not-me-its-them&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#its-not-me-its-them&quot; aria-label=&quot;its not me its them permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;It’s not me, it’s them&lt;/h2&gt;
&lt;p&gt;Maybe it’s sweet enough for you, but you may also say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But Oskar, I’m not producing those events, it’s the other team and the external service. If I was responsible for that, I’d go this way, but I can’t change it how messages are published.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I could handwave it and say I pity you, but well, this actually can happen. Let’s see what else we could do about it.&lt;/p&gt;
&lt;p&gt;The first idea could be: Let’s add timestamps!&lt;/p&gt;
&lt;p&gt;Let’s see how it looks for our example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;11:40:10 - ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
11:40:10 - ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
11:42:13 - ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
11:43:18 - ItemAddedToCart     (cartId: 1, name: Spaghetti Carbonara)
11:44:23 - ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
11:44:27 - CartConfirmed       (cartId: 1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the out of order delivery:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;11:40:10 - ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
11:40:10 - ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
11:44:23 - ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
11:44:27 - CartConfirmed       (cartId: 1, confirmedAt: 2025-11-03 11:44:27)
11:43:18 - ItemAddedToCart     (cartId: 1, name: Spaghetti Carbonara)
11:42:13 - ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Would that help? No, because how would we know, based on timestamps, that there will be two more events after confirmation? Timestamps only tell us when a certain operation happens. They could help us order items that were delivered, but they won’t help us know what we’re missing. Because how do we know that there’s a gap in our knowledge? Within a minute, one could do nothing and order or remove a few more items. Also, timestamps might work if the data is coming from the same node, but we don’t have any guarantees across nodes. Read more about &lt;a href=&quot;https://en.wikipedia.org/wiki/Clock_drift&quot;&gt;clock drift&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What we actually need is the logical clock. One that increments after each operation. So something like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;1 - ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
2 - ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
3 - ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
4 - ItemAddedToCart     (cartId: 1, name: Spaghetti Carbonara)
5 - ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
6 - CartConfirmed       (cartId: 1)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we had such, then our delivery would look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;2 - ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
1 - ItemAddedToCart     (cartId: 1, name: Pizza Napoletana)
5 - ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)
6 - CartConfirmed       (cartId: 1, confirmedAt: 2025-11-03 11:44:27)
4 - ItemAddedToCart     (cartId: 1, name: Spaghetti Carbonara)
3 - ItemRemovedFromCart (cartId: 1, name: Pizza Napoletana)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If this number were monotonic and gapless, we’d know that if the event has number 3, then we have completeness of information if we received three events; if not, then we’re missing something.&lt;/p&gt;
&lt;p&gt;We still need to define the completion criteria and determine, from a business perspective, where we can make a decision or proceed to the next step. Here we know that we can start preparing a meal when the shopping cart is confirmed.&lt;/p&gt;
&lt;p&gt;In this case, we got the events in the following order: 2, 1, 5, 6.&lt;/p&gt;
&lt;p&gt;We know we’re missing events 3 and 4, so we need to wait for them. Only when we receive them can we proceed. Ok, but how to do it?&lt;/p&gt;
&lt;p&gt;What if we kept a list of pending events in our data model? Let’s try that!&lt;/p&gt;
&lt;p&gt;Our kitchen order could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KitchenOrder&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  productItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProductItem&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Incomplete&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Ready&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;InPreparation&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KitchenOrderCommand&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AddItem&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;RemoveItem&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Confirm&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When storing it, we could store it with additional metadata:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DocumentWithPendingCommands&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; State &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    lastProcessedRevision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    pendingCommands&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PendingCommand&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you see, besides the regular data, we have two other properties: pending commands and last processed revision.&lt;/p&gt;
&lt;p&gt;You can think about pending commands as your git repository on your local disk. It contains the list of all operations that you’ll eventually commit. The rest of the data is like the remote git repository. They will be updated when you push your changes there. Then, the last processed revision will be updated with the revision of the last applied command.&lt;/p&gt;
&lt;p&gt;The code for that workflow could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartEvent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DocumentWithPendingCommands&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
    KitchenOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    KitchenOrderCommand
  &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DocumentWithPendingCommands&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;KitchenOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; KitchenOrderCommand&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; metadata&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;data &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      lastProcessedRevision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      pendingCommands&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; KitchenOrder &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    productItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Incomplete&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      lastProcessedRevision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastProcessedRevision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      pendingCommands&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pendingCommands&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;mapToPendingCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handlePendingCommands&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; decide&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It takes the current state of the kitchen order. If it doesn’t exist, then we need to set it up with default data. We’re also appending a pending command that’s built from an event.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mapToPendingCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartEvent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PendingCommand&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;KitchenOrderCommand&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:int:ItemAddedToCart&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AddItem&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          revision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;revision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:int:ItemRemovedFromCart&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;RemoveItem&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          revision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;revision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;sc:int:CartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Confirm&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          revision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;revision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Why not use a regular event here? We could. The benefit is that then we’d have all the data stored as it came. This could make troubleshooting and correction easier. Downside? We’re coupling the event with our internal business logic. Also, keeping the whole event payload increases the size of our data. The choice is yours.&lt;/p&gt;
&lt;p&gt;As you see, we’re taking revision from the event metadata. We’ll get to that later, how to fill it on the producer side. Now, let’s focus on the workflow.&lt;/p&gt;
&lt;p&gt;Let’s see what processing pending items looks like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;handlePendingCommands&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DocumentWithPendingCommands&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;decide&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DocumentWithPendingCommands&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; metadata&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;data &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; commandsToHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getCommandsReadyToHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pendingCommands&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastProcessedRevision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Nothing to do see here, please disperse&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;commandsToHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; command &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; commandsToHandle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lastCommand &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; commandsToHandle&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;commandsToHandle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      lastProcessedRevision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lastCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;revision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      pendingCommands&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pendingCommands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;revision &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; lastCommand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;revision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We need to get the commands ready to be handled. For instance, if we already had commands with the following revisions 2, 6, 1, 4, and now we got an action with number 3, then that means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We can process actions 1, 2, 3, 4&lt;/li&gt;
&lt;li&gt;Action 6 remains, as we’re missing 5.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The filtering can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;getCommandsReadyToHandle&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  pending&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PendingCommand&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  lastProcessedRevision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PendingCommand&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    pending
      &lt;span class=&quot;token comment&quot;&gt;// filter out commands that have already been processed&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cmd&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; cmd&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;revision &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; lastProcessedRevision&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// sort by revision to ensure correct order&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;revision &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;revision&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// only take commands that are consecutive in terms of revision&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PendingCommand&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Command&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;acc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lastRevision &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; acc&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;acc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;revision&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;lastRevision &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;revision &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; lastRevision &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;acc&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; acc&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When we filter them out and have actions to process, we need to run the actual logic for each of them. For our case this could look like that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; KitchenOrderCommand&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  order&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; KitchenOrder&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; KitchenOrder &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AddItem&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;RemoveItem&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Incomplete&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updatedItems &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; current &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; updatedItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; multiplier &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AddItem&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; current &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; multiplier &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updated &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        updatedItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; updated&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        updatedItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updatedItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; quantity&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            quantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Confirm&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Incomplete&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Ready&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KitchenOrderCommand&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AddItem&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;RemoveItem&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Confirm&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After processing each command, we’re returning the document with the updated state, filtered-out processed commands, and the last processed revision set to the revision of the last processed command.&lt;/p&gt;
&lt;p&gt;Not that big a hassle as it may seem, but…&lt;/p&gt;
&lt;h2 id=&quot;whats-revision-and-how-to-get-it&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#whats-revision-and-how-to-get-it&quot; aria-label=&quot;whats revision and how to get it permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What’s revision and how to get it?&lt;/h2&gt;
&lt;p&gt;A revision needs to be created on the producer side. The best is to use Optimistic Concurrency for that. If you don’t know what optimistic concurrency or locking is, then you should. Check my intros:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/optimistic_concurrency_for_pessimistic_times/&quot;&gt;Optimistic concurrency for pessimistic times&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_use_etag_header_for_optimistic_concurrency/&quot;&gt;How to use ETag header for optimistic concurrency&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Essentially, if you’re:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;using a typical implementation of optimistic concurrency where each record/document change increments its version/revision,&lt;/li&gt;
&lt;li&gt;publishing an event after each business operation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then you can use this incremented state revision and pass it in your events metadata. Having that, you’d know precisely on which revision it was recorded and get gapless monotonic numeration. It’d also ensure that each message is produced in a certain order, as operations on the specific record will be sequential. Read also more on how revision can help in &lt;a href=&quot;/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/&quot;&gt;Dealing with Eventual Consistency and Idempotency in projections&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But Oskar, what if I have more than one record?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, then you either need to store multiple revisions. But this won’t help if you need to correlate data between them, as revision is monotonic and gapless for the specific record.&lt;/p&gt;
&lt;p&gt;What about global positions? Well, they’re useful for knowing the order of things, but they won’t help here, as they’re monotonic but may have gaps.&lt;/p&gt;
&lt;p&gt;Read more on why in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/how-does-kafka-know-what-was-the/&quot;&gt;How does Kafka know what was the last message it processed? Deep dive into Offset Tracking&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/lets_talk_about_positions_in_event_stores/&quot;&gt;Let’s talk about positions in event stores&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/ordering_in_postgres_outbox/&quot;&gt;How Postgres sequences issues can impact your messaging guarantees&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then you’re back to square one, and the &lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;previous article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h2&gt;
&lt;p&gt;Proper modelling in Event-Driven Architecture can spare you a lot of complicated implementation tricks.&lt;/p&gt;
&lt;p&gt;If you&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;define essential events for your process,&lt;/li&gt;
&lt;li&gt;ensure that they have completeness of information,&lt;/li&gt;
&lt;li&gt;shape contracts and communication between modules, respecting the internal and external split.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, when things get easier to handle, we can define conditions that tell us when to take action.&lt;/p&gt;
&lt;p&gt;Still, sometimes we may:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;have strict ordering needs,&lt;/li&gt;
&lt;li&gt;be using a queue that doesn’t give us an ordering guarantee,&lt;/li&gt;
&lt;li&gt;need to adjust to the other teams.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then, using revision can be a decent option to solve things in an organised way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you’re dealing with such issues, I’m happy to help you through consulting or mentoring. &lt;a href=&quot;mailto:oskar@event-driven.io&quot;&gt;Contact me&lt;/a&gt; and we’ll find a way to unblock you!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/dealing_with_race_conditions_in_eda_using_read_models/&quot;&gt;Dealing with Race Conditions in Event-Driven Architecture with Read Models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/the-order-of-things-why-you-cant/&quot;&gt;The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/internal_external_events/&quot;&gt;Internal and external events, or how to design event-driven API&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/&quot;&gt;Dealing with Eventual Consistency and Idempotency in MongoDB projections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/saga_process_manager_distributed_transactions/&quot;&gt;Saga and Process Manager - distributed processes in practice&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/predictable-identifiers-enabling/&quot;&gt;Predictable Identifiers: Enabling True Module Autonomy in Distributed Systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/dealing-with-eventual-consistency/&quot;&gt;Dealing with Eventual Consistency, and Causal Consistency using Predictable Identifiers&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/event_driven_distributed_processes_by_example/&quot;&gt;Event-driven distributed processes by example&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/workflow-engine-design-proposal-tell/&quot;&gt;Workflow Engine design proposal, tell me your thoughts&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_have_fun_with_typescript_and_workflow/&quot;&gt;How TypeScript can help in modelling business workflows&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_update_past_data_in_event_sourcing/&quot;&gt;Oops I did it again, or how to update past data in Event Sourcing&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/event_transformations_and_loosely_coupling/&quot;&gt;Event transformations, a tool to keep our processes loosely coupled&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/testing_asynchronous_processes_with_a_little_help_from_dotnet_channels/&quot;&gt;Testing asynchronous processes with a little help from .NET Channels&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Dealing with Race Conditions in Event-Driven Architecture with Read Models]]></title><description><![CDATA[My events came out of order! What should I do?! Are you familiar with the term “phantom record” and its benefits? No? Let me explain it to…]]></description><link>https://event-driven.io/en/dealing_with_race_conditions_in_eda_using_read_models/</link><guid isPermaLink="false">https://event-driven.io/en/dealing_with_race_conditions_in_eda_using_read_models/</guid><content:encoded>&lt;blockquote&gt;
&lt;p&gt;My events came out of order! What should I do?!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Are you familiar with the term “phantom record” and its benefits? No? Let me explain it to you today.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Everyone has a plan until they get punched in the mouth.&lt;/strong&gt; Design, architecture, and modelling are important, but it’s the actual code that reaches production. That’s also the place where we see all those nasty issues that we haven’t foreseen, like: race conditions, eventual inconsistency, idemNotency, etc.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6852c645507e912e86625c5444dafe9f/b09c1/tyson.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADiElEQVR42h2Ty1ObZRSHvzq0I22xUtoplGshXBIuzYVcSPhy45ZAICQhgRCIBFIIgTQJhAaD0IaBAp3qUO1l3FUHXIhTrV2oo2v/hC515UI3Ll0+vrr4zXuZOc857zm/V+qx9fGfTLITQ68Di9jrem1ozFb0Jhmt3ky/axRfcJrA5AwTQoFg5P/zwPA47rEgLk8AZZeBbpMVySSCjRYb+h4rBqHYlJf1qJ+lsB/ZYkWtNtA/4CadXmfnfpFcLk8qs87u3gELd5IUtoskkmna2rvR6GUkjdGCxWrDabdjk2WO40FOMjM8W5nGojeiVeuxyg6SyRTxeILs2j0iszEmI3OEo3ERO0jhowekshvIDhdSm8ZEl4DaHFY+f7TFZ+txNsf7+SI3x8Kkjx6NDqfVQSqVIRaLk88XmIvdITwdJZ3JMS3AkfAMw+4xtN29SEO9Mumwjx+f7vDX90/YjQVxGrt5OjvCn68+5vSTArmwl+f7RQrLCTZXkjzc3uZBYYuCeH5RVGe32FHcUtKlNiL1mWXysyF+f1nk7WGCbxJ+fshFOEsGOMtM8vN+kpDHzacH++zk8+TXc6wmV9k7OCST2yS+ksblC9HUphb9FsA/kjHOoiEex3z8JACvlnx8uxxgb2KAg/AQv+7OM2SVGepzMWTrZ3zQg7fPTXQ8wJI/xD3/BFmPl4DWQNTmRPpn1M3XBiN1Le0sexy8mBkm1S8G1dHJ9qiD345TPJwcpWiReWx3cL/HQkZnZLb9NoHWLiZaOtA1KKmtU2IR1pPmTWYWhUbFYE6nxvhlepijkJvUiINjUeHrFR93g37evH7DVyenpNfzrG58yOGTZ7x4+SXLG1u8d1PBtVolDW3dSB6dCaNKzdhtHX8HvSB8+PbsOSdHGxS9djxGHY2tnSi1PbSrTTS1qqlpVFGvuE2dQk15VSvv31BQWl7PpfImpLKqJs5fr6dXo+c7m5Vihxqv3UlVYydXa1upUagoKavkfFkVlysaqKgWd02dtHQaUeksmJ0uPP5Jpj5YIJHOItW1qGkW38Zs76NR9KNEZDr3bjUXxFoqAEabg3RujeRahsX0XeYSi8wnl1jJZlkWgJmFRTwTYQxWFzrLIFJW+Gn36JBINELFzWYuVdyi5HItmi4twdEBBkcGUGnNNCh13Gjo4EplM6VX63nnYhVSyTXOXbhOycVKSq9UU9Og4l/KuPmN9fWgkAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/6852c645507e912e86625c5444dafe9f/507e6/tyson.webp 200w,
/static/6852c645507e912e86625c5444dafe9f/28a80/tyson.webp 400w,
/static/6852c645507e912e86625c5444dafe9f/0367d/tyson.webp 626w&quot;
              sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/6852c645507e912e86625c5444dafe9f/36ca5/tyson.png 200w,
/static/6852c645507e912e86625c5444dafe9f/a3397/tyson.png 400w,
/static/6852c645507e912e86625c5444dafe9f/b09c1/tyson.png 626w&quot;
            sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/6852c645507e912e86625c5444dafe9f/b09c1/tyson.png&quot;
            alt=&quot;tyson&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We realise that our BBC Architecture is just a Box-Box Cylinder on paper; in reality, things get messy. We go from a 2D layout to a 3D or even a 4D view.&lt;/p&gt;
&lt;p&gt;We see that our business processes are not so linear and predictable as we draw them. Of course, we have somewhere in the back of our mind that subprocesses can go in parallel, but somehow our tests go down the happy path. Then bugs and incidents come in.&lt;/p&gt;
&lt;p&gt;We learn the hard way that real processes have delays; they run in parallel. For instance, when you process a payment, fraud checking, risk assessment, and merchant validation happen simultaneously. That’s efficiency, not a flaw.&lt;/p&gt;
&lt;p&gt;And the sad part is that it’s our role to provide this efficiency. Yelling at event-driven clouds won’t help.&lt;/p&gt;
&lt;h2 id=&quot;race-conditions-in-eda&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#race-conditions-in-eda&quot; aria-label=&quot;race conditions in eda permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Race Conditions in EDA&lt;/h2&gt;
&lt;p&gt;When we distribute our systems, we’re getting better isolation of failure, we can deploy changes at a different pace, but… But we also get communication going at various paces. We can no longer get a unified view of the business actions in the order of appearance. Now each service has its own linearity.&lt;/p&gt;
&lt;p&gt;To integrate services predictably, we’re using messaging tools. They provide us with durability, retries when the recipient is unavailable, and ensure that information flows and data are delivered effectively. Still, they’re not magical creatures; they have their limits. We can’t cheat physics.&lt;/p&gt;
&lt;p&gt;Between one service and the other, we’re putting in a queue. That’s the place where we can achieve an ordering guarantee. Typically, one queue, one consumer means that we can achieve ordering. Still, not all tools are giving us that.&lt;/p&gt;
&lt;p&gt;Out of order issues may happen when the RabbitMQ queue has multiple consumers racing for messages. When we add consumers, we get better throughput, but we’re risking ordering issues.&lt;/p&gt;
&lt;p&gt;Out of order issues may also happen when you’re using tools like SQS or Google PubSub, which only guarantee best-effort ordering.&lt;/p&gt;
&lt;p&gt;Or when your outbox pattern deletes processed messages and loses sequence.&lt;/p&gt;
&lt;p&gt;Or when network delays shuffle carefully ordered streams.&lt;/p&gt;
&lt;p&gt;It’s safe to say that you won’t get any ordering guarantee between different queues. Since queues represent communication flows between modules, we should not assume strict ordering in cross-module communication.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c94972a72335077c07d38c1710f453d7/2977d/race-condition.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACWUlEQVR42mWT20/UQBTG92/22QQT44PxxcQHjQ+iiVHiJRqJLJAIiqBGQHARttuy21263c70Nr3ftt3P0yGSGJqczHSm853ffKenA3qYJbDZ7UFXVITHq4hchjCKkWYZEsdAcLSKJPSRZTnyPKcxuxZJksi9Tit4obt4trwL9eg3vI27sCcD2J6AyGq4ox7Yh9soE4GimqMsSywWi2tR17UU7nz7vgdNNbCzrUBVJ7SYwuIOzOEZ2OFHWKYB1/XAOYPnuTR3wRiD7/v/Rbuepik6Kysv8OtggKePt6ANpi0wyhrgyk+w97cQ+bYkq+sGGW00TSMpW5p23pK1UVXVJWG3uwalP8bejoZz7QJxHMlstuNABAGsmQmLcbjmCOxoHTMiDsIQts2J2JN0Qgg5SsE3b1/jcF/BEyLsn00koTSe8JsFURUFivkCgX5MxEtE7JBgRIljhCT8j7Clllfe2FhHvz/Cl60/RDhBFIUyM+c2hMNgKfuw2Qw+rQnfRSB86aFLNzDNqfRW+J48IwVbIk3lePRgG/1Tg94azOdUzQbw9ROMnt9A7M6Q5ORRXqBuFnKck6dFWSGnG+Q0VnSm/XWkYESemedD2IaBhPyIKWNMnkQ2Q2QOETk2vOkYYmbAM3SaTxDyGdypDmGZcC8uv8mvBIWC/d07YGaPfKMqlwUq8qSt3Lz11DOgv7oJ8eMl7M37cD49hPN1Gfb6PcQnXVjvlpDxEdK8vBRMYo6R9hkOdUVRVNKLqy6gAqXUJZ52gNBUIcYnCI1TRFMF8aSHhI8RDA+pkzxpwV8WFTCJFJYXoAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/c94972a72335077c07d38c1710f453d7/507e6/race-condition.webp 200w,
/static/c94972a72335077c07d38c1710f453d7/28a80/race-condition.webp 400w,
/static/c94972a72335077c07d38c1710f453d7/8d2ea/race-condition.webp 800w,
/static/c94972a72335077c07d38c1710f453d7/68fc1/race-condition.webp 1200w,
/static/c94972a72335077c07d38c1710f453d7/1a7d7/race-condition.webp 1444w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/c94972a72335077c07d38c1710f453d7/36ca5/race-condition.png 200w,
/static/c94972a72335077c07d38c1710f453d7/a3397/race-condition.png 400w,
/static/c94972a72335077c07d38c1710f453d7/a331c/race-condition.png 800w,
/static/c94972a72335077c07d38c1710f453d7/8537d/race-condition.png 1200w,
/static/c94972a72335077c07d38c1710f453d7/2977d/race-condition.png 1444w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/c94972a72335077c07d38c1710f453d7/a331c/race-condition.png&quot;
            alt=&quot;race condition&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;events-vs-rumours&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#events-vs-rumours&quot; aria-label=&quot;events vs rumours permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Events vs Rumours?&lt;/h2&gt;
&lt;p&gt;Continuing with the payment verification process example: Fraud module publishes to one queue. Risk assessment for another. The payment gateway to a third. Messages arrive at your module in the order your system received them, not the order they were created. Fraud detection service flags a high-risk payment at 10:00:01. The payment initiation event from the gateway, created at 10:00:00, arrives at 10:00:02. The fraud score arrives before the payment exists in your system. You might think sorting by timestamp solves this. It doesn’t. Clock skew between services means timestamps lie. Each service node has its own time. It may be similar, but if you have a system with high throughput, the skew may be significant enough, causing more issues than actual help.&lt;/p&gt;
&lt;p&gt;But, well, that’s the same case when you’re getting information from the outside world. You can get multiple friends sending you some news, you can get them at different paces, and read them in a different order. You may get obsolete news and then newer, but it can go the other way round. You’re only sure of which you received those messages; to get the truth, you need to correlate that information and do fact-checking based on some rules (e.g. which source is more reliable, newer, etc). Then you can deduce the actual information.&lt;/p&gt;
&lt;p&gt;We’re saying that in the event-driven world, events are facts. But that’s just half the truth.&lt;/p&gt;
&lt;p&gt;Those events that we store/publish are facts for us, or at least represent the current state of our knowledge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The events from external systems are rumours at best. We need to interpret them to make them (our) facts.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ok, but what can we do? Sit and cry?&lt;/p&gt;
&lt;p&gt;Of course, we can do more. We’ll discuss today a simple technique with Read Models that can take you far enough.&lt;/p&gt;
&lt;p&gt;Let’s say that you’re using Event Sourcing in your system. You take all the messages coming from other systems, and you store them in your event store.&lt;/p&gt;
&lt;p&gt;Event Sourcing assumes you can rebuild state by replaying events in sequence. When &lt;em&gt;FraudScoreCalculated&lt;/em&gt; arrives before &lt;em&gt;PaymentInitiated&lt;/em&gt;, the payment doesn’t exist. You can’t apply a fraud score to nothing. You realise that when your carefully designed domain model throws exceptions. This should never happen, aye?&lt;/p&gt;
&lt;p&gt;This isn’t specific to Event Sourcing. Even if you don’t use it but just update read models directly from events, you have the same problem. Your read model update handler for &lt;em&gt;FraudScoreCalculated&lt;/em&gt; looks for a payment document to update. No document exists. The update fails.&lt;/p&gt;
&lt;p&gt;Such issues are a recurring theme in my consulting, as they are a common concern for my customers. My advice is usually: Don’t try to fight them.&lt;/p&gt;
&lt;p&gt;Store data as it arrives and “denoise” on your side. Interpret them and save your own “facts”. Acknowledge the need for a &lt;a href=&quot;/en/internal_external_events/&quot;&gt;split between internal and external events&lt;/a&gt;. Use an Anti-Corruption Layer (ACL) pattern to protect from external chaos.&lt;/p&gt;
&lt;h2 id=&quot;read-models-as-anti-corruption-layer&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#read-models-as-anti-corruption-layer&quot; aria-label=&quot;read models as anti corruption layer permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Read models as Anti-Corruption Layer&lt;/h2&gt;
&lt;p&gt;Read models can be used as such an ACL. A read model document can have a partial state, with optional fields that are filled as events arrive. You can process each event, updating the fields it can, ignoring those it can’t, and making decisions based on the available data.&lt;/p&gt;
&lt;p&gt;Let’s explore how to apply this in practice using our payment orchestration scenario. Let’s say we’re gathering information about the payment verification process, displaying its state, and performing follow-up operations once the process is complete.&lt;/p&gt;
&lt;p&gt;When we trigger payment, we need to correlate data from the external payment gateway with our own modules, which calculate fraud scores, check limits, and assess risk.&lt;/p&gt;
&lt;p&gt;We could define events using TypeScript, as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentVerificationEvent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PaymentInitiated
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; FraudScoreCalculated
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; RiskAssessmentCompleted
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; MerchantLimitsChecked
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PaymentCompleted
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PaymentDeclined&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Having&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentInitiated&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PaymentInitiated&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    gatewayId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    initiatedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FraudScoreCalculated&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;FraudScoreCalculated&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    score&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    riskLevel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;low&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;medium&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;high&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    calculatedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RiskAssessmentCompleted&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;RiskAssessmentCompleted&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    riskScore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    factors&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    assessedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MerchantLimitsChecked&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;MerchantLimitsChecked&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    withinLimits&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    dailyRemaining&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    checkedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentCompleted&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PaymentCompleted&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; completedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentDeclined&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PaymentDeclined&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; declinedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s say the fraud system flagged the payment as high-risk before it even existed in your system. Approval happened before the risk assessment was completed. The example race condition can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;10:15:32.123 - FraudScoreCalculated (score: 85, high risk)
10:15:32.145 - PaymentInitiated (amount: $500)
10:15:32.167 - PaymentCompleted (approved by automated system)
10:15:32.201 - RiskAssessmentCompleted (risk: medium)
10:15:32.234 - MerchantLimitsChecked (within limits)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we assume that things go as on the whiteboard, and handlers can’t process events for non-existent payments, then we’ll be the ones who get punched in the mouth by reality. They will fail to find documents to update.&lt;/p&gt;
&lt;p&gt;The first step is to embrace that we have a problem. Actually, it’s not a problem, but rather a challenge - a scenario we just need to support.&lt;/p&gt;
&lt;p&gt;We can start by defining a document that represents a payment’s verification state. The document should include optional fields since you can’t predict which event will arrive first. The projection function processes each event and updates this document, creating it if necessary.&lt;/p&gt;
&lt;p&gt;It can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentVerification&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  payment&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Payment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  fraudAssessment&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; FraudAssessment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  riskEvaluation&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RiskEvaluation&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  merchantLimits&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MerchantLimits&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  decision&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Decision&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;unknown&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;processing&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;approved&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;declined&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  completionPercentage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  lastUpdated&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  dataQuality&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;partial&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sufficient&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;complete&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Payment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  gatewayId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  initiatedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FraudAssessment&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  score&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  riskLevel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;low&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;medium&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;high&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  assessedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RiskEvaluation&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  score&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  factors&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  assessedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MerchantLimits&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  withinLimits&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  dailyRemaining&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  checkedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Decision&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;approved&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; declined&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Besides the optional data that we’ll gradually fill as events arrive, we have some mandatory fields. The obvious one is paymentId, we need to be able to correlate upcoming data from events. If you have a look at them, all of them have such information. Thanks to that, we know which payments we are verifying. If we’re missing it, then we won’t be able to correlate upcoming data. Then we’re indeed doomed.&lt;/p&gt;
&lt;p&gt;Of course, sometimes things can get harder. We do not always have a certain id, sometimes some other data, like external id, idempotence key, correlation id, whatever id. Still, we need to have some field, or multiple fields that allow us to point to that for this event we need to update that document.&lt;/p&gt;
&lt;p&gt;How do we update our read model? We need a function that takes the current state (or null if it doesn’t exist) and the event we apply on top of it, returning the new state.&lt;/p&gt;
&lt;p&gt;It can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;evolve&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerificationEvent
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  current &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; current &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PaymentInitiated&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onPaymentInitiated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;FraudScoreCalculated&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onFraudScoreCalculated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;RiskAssessmentCompleted&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onRiskAssessmentCompleted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;MerchantLimitsChecked&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onMerchantLimitsChecked&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PaymentCompleted&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onPaymentCompleted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PaymentDeclined&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onPaymentDeclined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; initialState&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;unknown&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  completionPercentage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  lastUpdated&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  dataQuality&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;partial&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Using Emmett it could be defined as&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; paymentVerificationProjection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoSingleStreamProjection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// collection to where we store projection&lt;/span&gt;
  collectionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;paymentVerification&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;getDocumentId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  canHandle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;PaymentInitiated&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;FraudScoreCalculated&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;RiskAssessmentCompleted&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;MerchantLimitsChecked&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;PaymentApproved&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;PaymentDeclined&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// use this state when there&apos;s no document in collection of certain id&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;initialState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And yes, even if the data is not in the state we expect, we can always create the &lt;em&gt;&lt;strong&gt;Phantom Record&lt;/strong&gt;&lt;/em&gt; that represents the best state we can in the conditions we have.&lt;/p&gt;
&lt;p&gt;What if fraud scoring arrives first?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onFraudScoreCalculated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; FraudScoreCalculated&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Ignore event if we&apos;re already ahead of it&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculatedAt &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assessedAt&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    fraudAssessment&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      score&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;score&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      riskLevel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;riskLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      assessedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculatedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    lastUpdated&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculatedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;riskLevel &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;high&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;declined&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      decision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;declined&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;High fraud risk detected: score &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;score&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculatedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; updated&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I told you that you should not use a timestamp, but I did it on my own. We cannot always be prim and proper. When doing ACL, sometimes we can achieve just good enough results. If the event doesn’t have any &lt;a href=&quot;https://martinfowler.com/articles/patterns-of-distributed-systems/lamport-clock.html&quot;&gt;logical timestamp&lt;/a&gt;, then we need to live with what we have. Here, we’re assuming that &lt;em&gt;FraudScoreCalculated&lt;/em&gt; always comes from the same source, and then we assume that timestamps have the chance to be consistent. With that, we can use them to see if we haven’t already applied this information, or the newer one. If it’s older, then we just don’t make any changes. Sometimes you’ve got to do what you’ve got to do.&lt;/p&gt;
&lt;p&gt;Now, lets have a look on the payment initiation when fraud scoring might have already completed:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onPaymentInitiated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentInitiated&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Ignore if we already handled this event&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;payment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    payment&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currency&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      gatewayId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;gatewayId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      initiatedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;initiatedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    lastUpdated&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;initiatedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If fraud scoring was completed first, &lt;em&gt;current&lt;/em&gt; already has fraud data. The handler merges payment details into the current state.&lt;/p&gt;
&lt;p&gt;We can also mark that in the field that I called &lt;em&gt;dataQuality&lt;/em&gt;. It may not be the perfect name, but I used it to indicate that we can have a field that lets us decide whether data is as expected, or if we hit reality. Based on that, we can decide how to display it, or whether to display it at all. Thus, the name “phantom record”. It may look like phantom as it’s missing some data but it’s the best data we have.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onPaymentCompleted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentCompleted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Ignore if we already made decision&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decision&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; decision &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;riskLevel &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;high&apos;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;declined&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Approval attempted but overridden by fraud (score: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;score&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;approvedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;approved&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Approved by &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;approvedBy&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;approvedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; decision&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;approval&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    decision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    lastUpdated&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;approvedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;waiting-for-dependencies&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#waiting-for-dependencies&quot; aria-label=&quot;waiting for dependencies permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Waiting for Dependencies&lt;/h2&gt;
&lt;p&gt;Some decisions need multiple pieces. The &lt;em&gt;MerchantLimitsChecked&lt;/em&gt; handler waits for both fraud assessment and merchant limits. If we receive merchant limits first and don’t have a fraud assessment yet, we simply store the information; the same applies if it goes the other way. When we eventually get both, we can make the final update, e.g. about the final payment approval or decline.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onMerchantLimitsChecked&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MerchantLimitsChecked&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Ignore if we already handled this event&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;merchantLimits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    merchantLimits&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      withinLimits&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withinLimits&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      dailyRemaining&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dailyRemaining&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      checkedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    lastUpdated&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Check if we now have BOTH critical pieces&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;merchantLimits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Don&apos;t have both yet - stay in processing&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;processing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      dataQuality&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;processing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Both present - can make final decision&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; decision &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    updated&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;riskLevel &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;high&quot;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;declined&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;High fraud risk&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;merchantLimits&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withinLimits
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;declined&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;High fraud risk&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;approved&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Verified&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; decision&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;approval&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    decision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And now we’re reaching the grey area, where this can become an anti-pattern. I even made this mistake when writing this article. I wrote initially, &lt;em&gt;“we can make the final decision”&lt;/em&gt;, but then changed it to &lt;em&gt;“we can make the final update”&lt;/em&gt;. Why?&lt;/p&gt;
&lt;h2 id=&quot;projections-acl-responsibility-and-workflows&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#projections-acl-responsibility-and-workflows&quot; aria-label=&quot;projections acl responsibility and workflows permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Projections, ACL responsibility and Workflows&lt;/h2&gt;
&lt;p&gt;I’m sure you’ve used or seen Anti-Corruption Layers. They’re usually places where all the weird logic lands. We want to protect our core logic, pushing all hacks to one, hidden place. This strategy makes some sense; that’s why it’s called Anti-Corruption Layer. Yet…&lt;/p&gt;
&lt;p&gt;Yet, we should not push it to the limits, as we’ll end up with an unmaintainable beast. With the last example, we’ve reached or even passed the limits of what projection should be responsible for. The projection should just interpret upcoming information and store the result. It should not make decisions. Business logic is responsible for making decisions.&lt;/p&gt;
&lt;p&gt;What we actually ended up with in the last step is a form of &lt;a href=&quot;/en/workflow-engine-design-proposal-tell/&quot;&gt;process manager, or workflow&lt;/a&gt;, so a state machine that listens to events, gets its current state and makes further decisions. And what’s the best way to inform the outside world about making new decisions? Well, producing a new event.&lt;/p&gt;
&lt;p&gt;We could define an event as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentVerificationCompleted&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PaymentVerificationCompleted&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;approved&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; declined&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As we’re making decisions, let’s be frank and rename &lt;em&gt;evolve&lt;/em&gt; function to &lt;em&gt;decide&lt;/em&gt; and produce a new event when we distilled it from the messy outside world?&lt;/p&gt;
&lt;p&gt;It could look like that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerificationEvent
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PaymentVerification
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; VerificationEvent&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  current &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; current &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// (...) other event handlers&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;MerchantLimitsChecked&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        merchantLimits&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          withinLimits&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withinLimits&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          dailyRemaining&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dailyRemaining&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          checkedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        lastUpdated&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tryCompleteVerification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;FraudScoreCalculated&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculatedAt &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assessedAt
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updated &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        fraudAssessment&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          score&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;score&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          riskLevel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;riskLevel&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          assessedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculatedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        lastUpdated&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculatedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tryCompleteVerification&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updated&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We update the state and try to complete verification if we gathered both merchant limits and risk assessment. This could be encapsulated in a dedicated method, as the logic is the same now matter which event came first:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tryCompleteVerifications&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  current&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerificationEvent
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PaymentVerification
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentVerification&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; VerificationEvent&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Ignore if we already made decision&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;decision&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; current&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Check if we now have BOTH critical pieces&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;merchantLimits&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Don&apos;t have both yet - stay in processing&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;processing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      dataQuality&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;processing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; decision &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fraudAssessment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;riskLevel &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;high&quot;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;declined&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;High fraud risk&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;merchantLimits&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;withinLimits
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;declined&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;High fraud risk&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          approval&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;approved&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Verified&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          decidedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;current&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; decision&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;approval&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      decision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PaymentVerificationCompleted&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; decision&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re returning not only the new state, but also the new event. It can be published to the local module queue or just stored inside the event store.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Essentially, we’re making a chaotic outside world linear based on the order of our observations.&lt;/strong&gt; We can’t change the outside world, but we can at least know why and where we’ve made our decisions.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5619b5b0e5c0b3cb62d4f3230fa725dc/dd04a/linearity.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 40%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABgUlEQVR42pWSW28TMRBG8/+fioQEBVXwgtRUpUV5SIuSXhJECBsF2FygRW0SclO6DUpSwu561/Zh1rTAE1ItHdkae775xnYOLNZqjEkFcy+01qRpKvmWu5HLxD5/eka3uc2ouMGi8ZL56WOu63mCyhbBySYL/4D58UOCM4/DQsBZN2Cnt6Q9bTEfvuDmx/pWzpIzRjPuHzG8qHLVeM3y3OP6Y5nV+XsW7So3nVPW/RbfJbYc9+j4K2bTFc1gTe+qTzB54wQzt84hGJq15zS8XTYu9tidVHl0WWBnXGFrUHTrg5nHA9mrTzsuSYsJrEGpFJVoJ5Yk6m/LX/x92q0i25eHHI3r7PVKlEY1CoMT9vtl3k6a5GXvw6hDEirCMORnGBHFEVmHsUqI1K1gVulr95VQwsRy0XEic4qOBJlNLBfvYuIiVpKsxNlvYhFU2rIa+AzLTyUWi0Op8K7yBK+Wd4ezqv8l+gdxqlLL+pvP7HjTxXJ3z509/X2/zR+sdfkZvwAFqlSKT9eoFQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5619b5b0e5c0b3cb62d4f3230fa725dc/507e6/linearity.webp 200w,
/static/5619b5b0e5c0b3cb62d4f3230fa725dc/28a80/linearity.webp 400w,
/static/5619b5b0e5c0b3cb62d4f3230fa725dc/8d2ea/linearity.webp 800w,
/static/5619b5b0e5c0b3cb62d4f3230fa725dc/68fc1/linearity.webp 1200w,
/static/5619b5b0e5c0b3cb62d4f3230fa725dc/312f1/linearity.webp 1211w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5619b5b0e5c0b3cb62d4f3230fa725dc/36ca5/linearity.png 200w,
/static/5619b5b0e5c0b3cb62d4f3230fa725dc/a3397/linearity.png 400w,
/static/5619b5b0e5c0b3cb62d4f3230fa725dc/a331c/linearity.png 800w,
/static/5619b5b0e5c0b3cb62d4f3230fa725dc/8537d/linearity.png 1200w,
/static/5619b5b0e5c0b3cb62d4f3230fa725dc/dd04a/linearity.png 1211w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5619b5b0e5c0b3cb62d4f3230fa725dc/a331c/linearity.png&quot;
            alt=&quot;linearity&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This is actually the same pattern I showed during the webinar on modelling and implementing distributed processes:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/uURh5ziGfb8?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;We aggregate data until we have enough of it to make the next decision. Is it perfect? Nah, but sometimes best you can is good enough.&lt;/p&gt;
&lt;h2 id=&quot;when-this-approach-works-and-when-it-doesnt&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#when-this-approach-works-and-when-it-doesnt&quot; aria-label=&quot;when this approach works and when it doesnt permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;When This Approach Works (And When It Doesn’t)&lt;/h2&gt;
&lt;p&gt;This approach works when:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your business logic can handle partial data&lt;/strong&gt;: Payment approval can often proceed with fraud score and limits check, even if risk assessment is pending. E-commerce fulfilment can start when payment is confirmed, even if the recommendation engine results are still processing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eventual consistency is acceptable&lt;/strong&gt;: The payment dashboard might show “processing” for a few hundred milliseconds while verification completes. Most business users can tolerate brief inconsistency in exchange for system responsiveness.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Decisions can be corrected or refined&lt;/strong&gt;: A payment approved with partial data can be flagged for additional review when complete risk data arrives. An order can be expedited or delayed based on a complete customer analysis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You have clear business rules for conflicts&lt;/strong&gt;: When fraud assessment contradicts approval, fraud wins. When risk assessment arrives late, it updates data quality but doesn’t reverse committed decisions.&lt;/p&gt;
&lt;p&gt;This approach struggles when:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Perfect consistency is required&lt;/strong&gt;: Financial accounting, legal compliance, and safety-critical systems often can’t tolerate any inconsistency, even briefly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Business logic requires complete data&lt;/strong&gt;: Some decisions genuinely can’t be made with partial information. Credit limit increases might require a complete financial analysis before any approval.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rollback costs are high&lt;/strong&gt;: If reversing a decision is expensive or impossible, you need stronger ordering guarantees before committing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Users can’t tolerate uncertainty&lt;/strong&gt;: Some interfaces need to show definitive status immediately, not “processing” or “partial data available.”&lt;/p&gt;
&lt;h2 id=&quot;conclusion-embracing-the-chaos&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion-embracing-the-chaos&quot; aria-label=&quot;conclusion embracing the chaos permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion: Embracing the Chaos&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The world is chaotic, and we can’t stop the chaos, but we can stop fighting the chaos.&lt;/strong&gt; External events are rumours about what happened in other systems. Your read model can store the information you derive from those rumours. The evolve function processes whatever arrives, in whatever order, building state incrementally, making your local interpretation.&lt;/p&gt;
&lt;p&gt;Undeniably, it’s a workaround of some sort. But it’s also an acknowledgement that distributed systems don’t guarantee order across boundaries. When you can’t control the topology, you adapt on your side. Store data as it arrives. Denoise in your projections. Create clean internal events for downstream systems.&lt;/p&gt;
&lt;p&gt;Sometimes the proper solution is fixing your message topology. Route related events to the same partition. Use predictable identifiers for correlation. But when external systems constrain your options, when organisational boundaries limit your control, when legacy integrations force your hand - you’ve got to do what you’ve got to do.&lt;/p&gt;
&lt;p&gt;Build your local models and live with partial state. Process events in any order. Make decisions with available data. That’s how you build reliable systems on unreliable foundations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you’re dealing with such issues, I’m happy to help you through consulting or mentoring. &lt;a href=&quot;mailto:oskar@event-driven.io&quot;&gt;Contact me&lt;/a&gt; and we’ll find a way to unblock you!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/the-order-of-things-why-you-cant/&quot;&gt;The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/internal_external_events/&quot;&gt;Internal and external events, or how to design event-driven API&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/&quot;&gt;Dealing with Eventual Consistency and Idempotency in MongoDB projections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/saga_process_manager_distributed_transactions/&quot;&gt;Saga and Process Manager - distributed processes in practice&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/predictable-identifiers-enabling/&quot;&gt;Predictable Identifiers: Enabling True Module Autonomy in Distributed Systems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/dealing-with-eventual-consistency/&quot;&gt;Dealing with Eventual Consistency, and Causal Consistency using Predictable Identifiers&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/event_driven_distributed_processes_by_example/&quot;&gt;Event-driven distributed processes by example&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/workflow-engine-design-proposal-tell/&quot;&gt;Workflow Engine design proposal, tell me your thoughts&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_have_fun_with_typescript_and_workflow/&quot;&gt;How TypeScript can help in modelling business workflows&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_update_past_data_in_event_sourcing/&quot;&gt;Oops I did it again, or how to update past data in Event Sourcing&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/event_transformations_and_loosely_coupling/&quot;&gt;Event transformations, a tool to keep our processes loosely coupled&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/testing_asynchronous_processes_with_a_little_help_from_dotnet_channels/&quot;&gt;Testing asynchronous processes with a little help from .NET Channels&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/en/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[On Messaging and Distributed Systems with Ian Cooper]]></title><description><![CDATA[Welcome to the new week! New week, new video. Last week, you got my talk about event-driven modelling anti-patterns. A significant portion…]]></description><link>https://event-driven.io/en/on-messaging-and-distributed-systems/</link><guid isPermaLink="false">https://event-driven.io/en/on-messaging-and-distributed-systems/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;New week, new video. Last week, you got &lt;a href=&quot;/en/new-recording-on-event-modelling/&quot;&gt;my talk about event-driven modelling anti-patterns&lt;/a&gt;. A significant portion of it was to explain the difference between logical design and technical one.&lt;/p&gt;
&lt;p&gt;Event-Driven Architecture helps us to design interactions in our system. We prefer async communication, informing others of what has happened by publishing events. Yet, if we model our interactions only with events, then we’d either end up with a passive-aggressive environment or a room filled with shouting people. That’s not great.&lt;/p&gt;
&lt;p&gt;Messaging patterns help us to design our message flow optimally.&lt;/p&gt;
&lt;p&gt;One of the best people in the messaging space is Ian Cooper. I have a lot of respect for his work and knowledge. He always does his homework.&lt;/p&gt;
&lt;p&gt;Last week, I had the pleasure of recording an interview with him, thanks to an invitation from &lt;a href=&quot;https://www.avanscoperta.it/en/&quot;&gt;Avanscoperta&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/0NYwN_p2pFI?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;We discussed Ian’s perspective on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Why do we still need to learn messaging? Why isn’t it a commodity yet?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;how we both started with messaging,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;how Ian is learning and teaching others about distributed systems and messaging,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;how to shape boundaries, and how data on the insight, and data on the outside can help in that,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;testing strategies,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and more.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The trigger for discussion was Ian’s upcoming workshop, but I’m sure that if you’re looking for a lighthearted exchange of ideas on those topics, I think you’ll enjoy it.&lt;/p&gt;
&lt;p&gt;You can also check it on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://open.spotify.com/episode/2O0C9QoefQzyaLs5v86HZq?si=1h1UxO7qTAmXTgJX1CUt1A&amp;#x26;nd=1&amp;#x26;dlsi=0b0c2b91f4f34af9&quot;&gt;Spotify&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://podcasts.apple.com/it/podcast/distributed-systems-messaging-patterns-with-ian-cooper/id1509214035?i=1000730424403&quot;&gt;Apple Podcasts&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please share your thoughts with me on how you liked it!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[New Recording on Event modelling anti-patterns from DDDEU]]></title><description><![CDATA[Welcome to the new week! I’m always saying that with Event-Driven Architecture, the modelling effort pays back. That goes both ways: the…]]></description><link>https://event-driven.io/en/new-recording-on-event-modelling/</link><guid isPermaLink="false">https://event-driven.io/en/new-recording-on-event-modelling/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m always saying that with Event-Driven Architecture, the modelling effort pays back.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That goes both ways: the less effort we put in modelling, the more it’ll hurt later.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There’s a wide range of issues you may be facing, from overfocusing on the state instead of tracking behaviour, to asking others more often than allowing them to tell you what happened, and ending with race conditions and other unpleasant scenarios.&lt;/p&gt;
&lt;p&gt;I packed as many of such cases into our talk, and the recording from this year’s DDD Europe just arrived:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/Lf1MZlpbkGA?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;During the session, I explained the specifics of event modelling (yes, no capital letter, and double l), starting with bad practices and knowing why and how to avoid them.&lt;/p&gt;
&lt;p&gt;I told the story about the project that aimed to modernise legacy software into the event-driven world. In theory, artificial, but in practice, none of the examples were made up. Either I made those mistakes on my own, or I saw them in my projects or helped to fix them for my clients.&lt;/p&gt;
&lt;p&gt;I tried to make it both entertaining and educational, bitter and sweet. It is not easy when you’re not a native speaker. There’s a thin line between being funny and being silly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There’s also a thin line between bad and good practices. And its name is:&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;context&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Also, as much some of those cases may seem wild, then I can assure you that all of those mistakes I either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;did by myself,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;saw in my projects,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;saw in my client’s project.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check it out, why learn always from your mistakes? Learn from mine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The talk also summarised my article series about anti-patterns in event modelling. Here’s the full list:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/state-obsession/&quot;&gt;State Obsession&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/property-sourcing/&quot;&gt;Property Sourcing&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/i_will_just_add_one_more_field/&quot;&gt;I’ll just add one more field&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/clickbait_event/&quot;&gt;Clickbait event&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/one_or_more_event_that_is_the_question/&quot;&gt;Should you record multiple events from business logic?&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/on_putting_stream_id_in_event_data/&quot;&gt;Stream ids, event types prefixes and other event data you might not want to slice off&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Check also more general considerations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/events_should_be_as_small_as_possible/&quot;&gt;Events should be as small as possible, right?&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/whats_the_difference_between_event_and_command/&quot;&gt;What’s the difference between a command and an event?&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/internal_external_events/&quot;&gt;Internal and external events, or how to design event-driven API&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/event_streaming_is_not_event_sourcing/&quot;&gt;Event Streaming is not Event Sourcing!&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/dont_let_event_driven_architecture_buzzwords_fool_you/&quot;&gt;Don’t let Event-Driven Architecture buzzwords fool you&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how_to_design_software_architecture_pragmatically/&quot;&gt;How to design software architecture pragmatically&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/gdpr_in_event_driven_architecture/&quot;&gt;How to deal with privacy and GDPR in Event-Driven systems&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;And hey, I’m also doing consulting and mentoring. If you’re struggling with your projects/organisation with such cases, I’m happy to help. Feel free to reach out to me through the &lt;a href=&quot;mailto:oskar@event-driven.io&quot;&gt;email&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Sneaky Code Bites Back]]></title><description><![CDATA[Welcome to the new week! Sneaky code bites back, I realised that again recently. I realised I’d been writing terrible code when I couldn’t…]]></description><link>https://event-driven.io/en/sneaky-code-bites-back/</link><guid isPermaLink="false">https://event-driven.io/en/sneaky-code-bites-back/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;Sneaky code bites back, I realised that again recently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I realised I’d been writing terrible code when I couldn’t explain it to myself.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The code worked. It was adding SQLite support to &lt;a href=&quot;https://github.com/event-driven-io/pongo&quot;&gt;Pongo&lt;/a&gt;. But when I tried to describe how I was implementing multi-database support, I heard myself saying:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“First it checks if the Promise is cached, then it creates a proxy that defers to the real implementation which doesn’t exist yet, but will be loaded dynamically when…”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 780px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f19f597fd614bb118adbb70337af1d26/d77b7/2025-09-22-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMBAgX/xAAWAQEBAQAAAAAAAAAAAAAAAAACAAH/2gAMAwEAAhADEAAAATOVBVxRZ//EABsQAAEEAwAAAAAAAAAAAAAAAAEAAgMRECEy/9oACAEBAAEFApKqQ6Q6dj//xAAWEQADAAAAAAAAAAAAAAAAAAAQESH/2gAIAQMBAT8BcH//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPwGH/8QAGBAAAgMAAAAAAAAAAAAAAAAAARARIEH/2gAIAQEABj8CjaBf/8QAGhABAAIDAQAAAAAAAAAAAAAAAQAhEBFBUf/aAAgBAQABPyFHch47iPIqMvbH/9oADAMBAAIAAwAAABBw7//EABcRAQADAAAAAAAAAAAAAAAAAAABESH/2gAIAQMBAT8QjRb/xAAWEQEBAQAAAAAAAAAAAAAAAAAAETH/2gAIAQIBAT8Q0j//xAAaEAEAAwEBAQAAAAAAAAAAAAABABEhMVGx/9oACAEBAAE/EHSRy3xFS0KGehFYk1NjbNvrE7Lt2KjjP//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/f19f597fd614bb118adbb70337af1d26/507e6/2025-09-22-cover.webp 200w,
/static/f19f597fd614bb118adbb70337af1d26/28a80/2025-09-22-cover.webp 400w,
/static/f19f597fd614bb118adbb70337af1d26/8e518/2025-09-22-cover.webp 780w&quot;
              sizes=&quot;(max-width: 780px) 100vw, 780px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/f19f597fd614bb118adbb70337af1d26/37402/2025-09-22-cover.jpg 200w,
/static/f19f597fd614bb118adbb70337af1d26/4cda9/2025-09-22-cover.jpg 400w,
/static/f19f597fd614bb118adbb70337af1d26/d77b7/2025-09-22-cover.jpg 780w&quot;
            sizes=&quot;(max-width: 780px) 100vw, 780px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/f19f597fd614bb118adbb70337af1d26/d77b7/2025-09-22-cover.jpg&quot;
            alt=&quot;Nicolas Cage&amp;#39;s Most Outlandish Roles Ever&quot;
            title=&quot;Nicolas Cage&amp;#39;s Most Outlandish Roles Ever&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Yes, I sounded insane.&lt;/p&gt;
&lt;h2 id=&quot;what-i-was-building&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-i-was-building&quot; aria-label=&quot;what i was building permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;What I Was Building&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Pongo is my attempt to bring the familiar MongoDB API and use relational databases as document databases.&lt;/p&gt;
&lt;p&gt;If you’ve used MongoDB, you know the API collection.find(), collection.insertOne(), collection.updateOne(). Pongo gives you that same API, but your data lives in &lt;a href=&quot;/en/postgresql-jsonb-powerful-storage/&quot;&gt;PostgreSQL using JSONB columns&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Why would anyone want this? Teams often know MongoDB’s API but need PostgreSQL’s guarantees: real ACID transactions, mature replication, and existing infrastructure. Or they have PostgreSQL but want document storage without learning PostgreSQL’s JSON query syntax, which looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; users

&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; jsonb_path_exists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;$.address.history[*] ? (@.street == &quot;Elm St&quot;)&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;users.find({ &quot;address.history&quot;: { $elemMatch: { street: &quot;Elm St&quot; } } })&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Same result, familiar syntax. I’d built something similar before with Marten for .NET, so I knew the approach worked.&lt;/p&gt;
&lt;p&gt;It seemed that people appreciated it. And when someone appreciates something, they usually want more…&lt;/p&gt;
&lt;p&gt;Users started requesting support for other databases, e.g. SQLite for edge deployments, DuckDB for analytics, Spanner for distributed systems, etc. Made sense. The MongoDB API could work on any database that supports JSON.&lt;/p&gt;
&lt;h2 id=&quot;the-challenge&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-challenge&quot; aria-label=&quot;the challenge permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Challenge&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Adding multi-database support meant dealing with drivers. Database drivers are how your application talks to databases. So code code handling connection protocols, type conversions, and query preparation. Each database needs its own driver.&lt;/p&gt;
&lt;p&gt;The straightforward approach: make developers explicitly import the driver they need:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pongoClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/pongo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; databaseDriver &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/pongo/pg&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
 driver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; databaseDriver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

 connectionString&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;postgresql://localhost:5432/mydb&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This bothered me. The connection string already says ”&lt;code class=&quot;language-text&quot;&gt;postgresql&lt;/code&gt;“.&lt;/p&gt;
&lt;p&gt;Why make developers say it twice?&lt;/p&gt;
&lt;p&gt;I wanted automatic driver selection. Parse the connection string, load the right driver, only when needed.&lt;/p&gt;
&lt;h2 id=&quot;the-sneaky-solution&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-sneaky-solution&quot; aria-label=&quot;the sneaky solution permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Sneaky Solution&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I built a deferred loading pattern in Dumbo, my database abstraction layer. Dumbo sits under Pongo, handling connection pools, transactions, and SQL generation. Both Pongo and my event sourcing library, &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt;, use it.&lt;/p&gt;
&lt;p&gt;Here’s the pattern:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; createDeferredConnectionPool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Connector&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ConnectionType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  connector&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Connector&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;importPool&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ConnectionPool&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Connection&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Connector&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConnectionPool&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ConnectionType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; poolPromise&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ConnectionPool&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getPool&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;poolPromise&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; poolPromise&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;poolPromise &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;importPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createConnectionPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    connector&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    execute&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createDeferredExecutor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connector&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;poolPromise&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; poolPromise&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ... every method proxied through getPool()&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let me explain what’s happening here, because it’s the core of why my design went wrong.&lt;/p&gt;
&lt;p&gt;The connection pool appears to exist immediately, but it’s actually a proxy. Every method (&lt;code class=&quot;language-text&quot;&gt;execute&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;close&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;transaction&lt;/code&gt;, etc.) is wrapped to the first call &lt;code class=&quot;language-text&quot;&gt;getPool()&lt;/code&gt;. The first time &lt;code class=&quot;language-text&quot;&gt;getPool()&lt;/code&gt; runs, it imports the real driver and creates the real connection pool. Then—and this is crucial—it caches the Promise itself, not the result.&lt;/p&gt;
&lt;p&gt;In JavaScript, a Promise is an object. Once created, it has an identity. You can await the same Promise multiple times, and the function will be evaluated once, the result will be cached and reused in subsequent awaits:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; work &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doSomethingAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; work&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; work&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Same Promise object, same eventual result&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is different from calling the async function twice:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doSomethingAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Creates Promise #1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; b &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;doSomethingAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Creates Promise #2, does work again&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;By caching the Promise, multiple parts of code can request the connection pool simultaneously, but the import only happens once.&lt;/strong&gt; Everyone waits for the same Promise to resolve. This pattern is called Promise memoisation.&lt;/p&gt;
&lt;p&gt;In C# terms, it’s like storing a &lt;code class=&quot;language-text&quot;&gt;Task&amp;lt;T&gt;&lt;/code&gt; and having multiple threads await it. In Python, it’s like &lt;code class=&quot;language-text&quot;&gt;functools.lru_cache&lt;/code&gt; for async functions. The concept exists across languages—cache the operation, not just the result.&lt;/p&gt;
&lt;p&gt;This worked in Dumbo. The API stayed clean. Drivers are loaded on demand. You pay the differing cost only once.&lt;/p&gt;
&lt;p&gt;Then I tried extending this pattern to Pongo itself.&lt;/p&gt;
&lt;h2 id=&quot;the-cascade-effect&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-cascade-effect&quot; aria-label=&quot;the cascade effect permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Cascade Effect&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;To support automatic driver selection in Pongo, I’d need to defer everything.&lt;/p&gt;
&lt;p&gt;Currently, when you create a Pongo client, it knows its driver:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; connectionString &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; db &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;myapp&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;users&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each level—client, database, collection—is a concrete object with real methods.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With automatic driver selection, none of these could be real until first use.&lt;/strong&gt; The client wouldn’t know which driver to use until it parsed the connection string. However, parsing occurs asynchronously, as dynamic module loading in JavaScript can only happen with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import#import_a_module_for_its_side_effects_only&quot;&gt;async imports&lt;/a&gt;. So the client also needs to become a proxy just like the Dumbo connection. The database becomes a proxy. The collection becomes a proxy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Here’s what a deferred collection would look like:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DeferredPongoCollection&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PongoCollection&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; realCollectionPromise&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PongoCollection&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getDb&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PongoDb&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getRealCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PongoCollection&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;realCollectionPromise&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;realCollectionPromise &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDb&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;db &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;realCollectionPromise&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Filter&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; FindOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRealCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Filter&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; FindOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRealCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;InsertOneResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRealCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;insertMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;docs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;InsertManyResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRealCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;docs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Filter&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; update&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Update&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;UpdateResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRealCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; update&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;updateMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Filter&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; update&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Update&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;UpdateResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRealCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; update&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// ... and 20+ more methods&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Every single method becomes a proxy that waits for the real collection. But the collection needs a database, which needs a client, which needs a driver. Three levels of deferred proxies.&lt;/p&gt;
&lt;p&gt;Now add TypeScript generics that need to flow through all these layers. Add error handling—what if the import fails? Add specialised MongoDB types like &lt;code class=&quot;language-text&quot;&gt;AggregationCursor&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;ChangeStream&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;BulkWrite&lt;/code&gt;. Each needs its own deferred proxy class.&lt;/p&gt;
&lt;p&gt;The driver would register itself through a side effect:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// In @event-driven-io/pongo/pg&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; registerDriver &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/pongo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;registerDriver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;./postgresqlDriver&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then you could add just the import anywhere in the app:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/pongo/pg&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And magic registration will happen, and as a user, you wouldn’t need to know about all the stuff I just wrote, right? RIGHT?!&lt;/p&gt;
&lt;p&gt;Right, if you remember about this import, if you forgot, then the registration doesn’t happen. The error only surfaces at runtime when you try to connect.&lt;/p&gt;
&lt;h2 id=&quot;the-failure&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-failure&quot; aria-label=&quot;the failure permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Failure&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;When I was into this design, I reminded myself on a similar thing I wanted to do in past.&lt;/p&gt;
&lt;p&gt;Years ago, I nearly shipped distributed transactions using &lt;a href=&quot;https://en.wikipedia.org/wiki/Microsoft_Distributed_Transaction_Coordinator&quot;&gt;MSDTC (Microsoft’s distributed transaction coordinator).&lt;/a&gt; The code worked perfectly in testing. But when I imagined production failures, I realised I couldn’t debug them. The complexity was hidden, not gone.&lt;/p&gt;
&lt;p&gt;This was the same mistake.&lt;/p&gt;
&lt;p&gt;Consider what happens when something goes wrong. A user reports:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I’m getting an error when inserting a document.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Where’s the actual problem? Let’s trace through the layers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;User calls &lt;code class=&quot;language-text&quot;&gt;collection.insertOne(doc)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deferred collection waits for real collection via &lt;code class=&quot;language-text&quot;&gt;getRealCollection()&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which waits for a deferred database via &lt;code class=&quot;language-text&quot;&gt;getDb()&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which waits for a deferred client to resolve&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which waits for the driver to be imported&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which needs the connection string to be parsed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which needs the driver to be registered&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which depends on a side-effect import that may not have happened&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The stack trace shows &lt;code class=&quot;language-text&quot;&gt;insertOne&lt;/code&gt; at the top. The actual error—maybe a typo in the connection string, maybe a missing driver import, maybe a network issue—is buried under seven layers of Promise resolution.&lt;/p&gt;
&lt;p&gt;But there’s a worse problem: Promise memoisation means errors get cached forever.&lt;/p&gt;
&lt;p&gt;If the first connection attempt fails, &lt;code class=&quot;language-text&quot;&gt;poolPromise&lt;/code&gt; holds a rejected Promise. Every subsequent request awaits the same rejected Promise. You can’t retry without restarting the process. This is a fundamental property of Promises—once settled (resolved or rejected), they never change state.&lt;/p&gt;
&lt;p&gt;In production, this means that one failed connection attempt during startup breaks everything until a restart is performed. A temporary network glitch becomes a permanent failure.&lt;/p&gt;
&lt;h2 id=&quot;the-nightmare&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-nightmare&quot; aria-label=&quot;the nightmare permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Nightmare&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Let’s say you’re debugging this in production. You add logging:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;InsertOneResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;insertOne called&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRealCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Got real collection&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The logs show “insertOne called” but not “Got real collection”. Where’s the problem? Could be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Driver not registered (missing import)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Connection string malformed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Network unreachable&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Import failed (syntax error in driver)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Previous connection attempt failed (cached rejection)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You need to understand the entire deferred loading chain to debug a simple insert.&lt;/p&gt;
&lt;p&gt;Now imagine explaining this to a contributor who wants to add a DuckDB driver.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“First, create your driver. Then register it via side effect. Understand Promise memoization because failures get cached. Know that everything is proxied through three levels of deferreds. When debugging, remember the stack trace lies about where errors originate…”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;the-alternative&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-alternative&quot; aria-label=&quot;the alternative permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Alternative&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I chose explicit driver injection instead:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pongoClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/pongo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; databaseDriver &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/pongo/pg&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  driver&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; databaseDriver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  connectionString&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;postgresql://localhost:5432/mydb&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One extra import. That’s the entire cost.&lt;/p&gt;
&lt;p&gt;The benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;TypeScript catches missing drivers at compile time: “Property ‘driver’ is missing”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stack traces point to real problems&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Failed connections can retry&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New developers understand it immediately&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each driver can expose database-specific features&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code is boring. No magic. No proxies. No deferred loading. It works or it doesn’t. When it fails, you know why and where.&lt;/p&gt;
&lt;h2 id=&quot;the-pattern&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-pattern&quot; aria-label=&quot;the pattern permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Pattern&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;This isn’t really about Pongo or database drivers. It’s about a pattern we all fall into.&lt;/p&gt;
&lt;p&gt;We see repetition and think “I can abstract this.”&lt;/p&gt;
&lt;p&gt;We see explicit configuration and think: &lt;em&gt;“I can infer this”&lt;/em&gt;. We see upfront costs and think: &lt;em&gt;“I can defer this”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Sometimes we just don’t trust our colleagues, thinking that they’re too incompetent and we need to take precautionary steps.&lt;/p&gt;
&lt;p&gt;Sometimes we’re right. Most often we’re not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Too often, we’re optimising the wrong metric. I was optimizing for fewer imports, not simpler code. I was hiding essential configuration, not accidental complexity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A minimal API with complex implementation isn’t simple—it’s a lie. The complexity doesn’t disappear. It moves from the visible surface to hidden internals, where it’s harder to understand, debug, and modify.&lt;/p&gt;
&lt;p&gt;I’ve seen this pattern everywhere:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ORMs with query optimization&lt;/strong&gt;: They generate “optimal” SQL you can’t debug. When queries are slow, you’re reverse-engineering what the ORM decided to do. The abstraction that was supposed to help becomes a barrier to fixing problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dependency injection with auto-wiring&lt;/strong&gt;: The container magically figures out what to inject where. Works great until it doesn’t. Then you’re debugging why the container chose the wrong implementation, and the magic configuration system has no good debugging tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build tools with convention over configuration&lt;/strong&gt;: They discover your project structure automatically. Perfect until your project doesn’t fit the convention. Then you’re fighting the tool’s assumptions, wishing for explicit configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test frameworks with magical setup&lt;/strong&gt;: They auto-mock dependencies, auto-setup databases, auto-configure everything. The tests become harder to understand than the code they’re testing. When tests fail, you debug the framework, not your code.&lt;/p&gt;
&lt;p&gt;The pattern is always the same. Good intentions. Hidden complexity. Eventual regret.&lt;/p&gt;
&lt;h2 id=&quot;when-magic-becomes-a-prison&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#when-magic-becomes-a-prison&quot; aria-label=&quot;when magic becomes a prison permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;When Magic Becomes a Prison&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Magic in code should be like magic in a good movie—the audience shouldn’t see how it works. But there’s a crucial difference: movie audiences never need to modify the magic trick.&lt;/p&gt;
&lt;p&gt;Code has maintainers. They need to understand the magic, extend it, debug it. When the magic is at the surface—the API level—every maintainer becomes a magician’s apprentice.&lt;/p&gt;
&lt;p&gt;The deferred loading pattern would have created a codebase where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Reading code means understanding proxies and Promise memoization&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adding features means maintaining the deferred chain&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Debugging means tracing through invisible layers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Onboarding means learning the entire magical system&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It creates a priesthood of maintainers who understand the magic. Everyone else fears the code. They make small changes and hope nothing breaks. They don’t refactor because they might disturb the magic.&lt;/p&gt;
&lt;p&gt;Let’s be honest about what I was trading:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The cost of explicit drivers:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;One extra import line&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Developers must know which database they’re using&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The cost of automatic driver selection:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Three levels of proxy objects&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Promise memoization complexity&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cached failures requiring restarts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Incomprehensible stack traces&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Side-effect registration&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Impossible debugging&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Steep learning curve for contributors&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Was saving one import worth all that complexity? Obviously not. But I didn’t see it until I started implementing it.&lt;/p&gt;
&lt;p&gt;This is why I need to write code to understand ideas. My brain can’t model all the implications abstractly. I need to “sweat out” designs in real code. Sometimes that means building something just to realize it’s wrong.&lt;/p&gt;
&lt;p&gt;The key is recognizing it before shipping.&lt;/p&gt;
&lt;h2 id=&quot;the-lesson&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-lesson&quot; aria-label=&quot;the lesson permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Lesson&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Every time I’ve tried to be sneaky, it’s bitten me. The distributed transactions that worked until they didn’t. The code generators that saved typing but destroyed readability. The clever abstractions that became maintenance nightmares.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The problems sneaky code solves are almost always smaller than the problems it creates.&lt;/strong&gt; But we don’t see that at design time. We see the elegance of the solution, not the pain of living with it.&lt;/p&gt;
&lt;p&gt;I wanted to make Pongo simple. I was making its setup simplistic and obscure. Simple means easy to understand and maintain. Simplistic means oversimplified to the point of being inadequate for real-world use.&lt;/p&gt;
&lt;p&gt;Now I’m removing the clever patterns. Each driver will be explicit. Each connection direct. Each error immediate and clear.&lt;/p&gt;
&lt;p&gt;The new code is boring. Import a driver. Pass it to the client. It connects or fails with a clear error.&lt;/p&gt;
&lt;p&gt;Boring code is debuggable. Explicit code is maintainable. Simple code—truly simple, not simplistic—is code that works and keeps working.&lt;/p&gt;
&lt;p&gt;I got too clever. We all do sometimes. The key is catching it before it escapes into production, where it becomes someone else’s nightmare.&lt;/p&gt;
&lt;p&gt;If you want to see “The Removal”, check &lt;a href=&quot;https://github.com/event-driven-io/Pongo/pull/126&quot;&gt;this Pull Request&lt;/a&gt; and related follow-ups. I’m replacing the clever code with boring code. It’s less exciting than it sounds, and that’s the point.&lt;/p&gt;
&lt;p&gt;And hey, there’s also another lesson: we all make mistakes, and fall into the rabbit hole. At least I do.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[PostgreSQL partitioning, logical replication and other Q&A about PostgreSQL Superpowers]]></title><description><![CDATA[Welcome to the new week! Last Tuesday, I did a new version of the “PostgreSQL Superpowers” webinar, thanks to the invitation from the good…]]></description><link>https://event-driven.io/en/postgresql-partitioning-logical-replication/</link><guid isPermaLink="false">https://event-driven.io/en/postgresql-partitioning-logical-replication/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;Last Tuesday, I did a new version of the “PostgreSQL Superpowers” webinar, thanks to the invitation from the good folks at &lt;a href=&quot;https://particular.net/&quot;&gt;Particular&lt;/a&gt;. It’s available here:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/cGy2XinTbkQ?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Or check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;The previous version I did here for our webinar&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/postgres_superpowers/&quot;&gt;The article going through the same narrative&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/oskardudycz/postgres-for-dotnet-dev&quot;&gt;complete source code of what I showed during the webinar&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m happy with how it went, as looking at the number of questions, I managed to pique people’s curiosity about this topic!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Curious enough, that I didn’t manage to cover all the questions during the webinar, which is why I decided to answer them today as part of my humble newsletter.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I encourage you to watch the recording, but to recap what I showed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PostgreSQL is not only a rock-solid Open Source database, but also how rich the plugin ecosystem is, and how that impacts its success&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;native PostgreSQL partitioning for efficient data storage management,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How to compose plugins like &lt;a href=&quot;https://www.linkedin.com/company/timescaledb/&quot;&gt;Timescale&lt;/a&gt; for time series data, PostGIS for spatial data (GPS tracking, etc.).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In general, how can plugins cut the time needed to deliver essential features&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automate reporting with continuous aggregations without having to poll the database&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stream database changes in real time by using logical replication as a push-notification system&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Detect issues as they happen, for example, spotting unusual fleet activity, without constant polling&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of that is based on the simple, but real-world app of tracking business trips and managing reporting and alerting around them.&lt;/p&gt;
&lt;p&gt;Ok, let’s move into the questions that I didn’t manage to cover during the webinar.&lt;/p&gt;
&lt;h2 id=&quot;questions--answers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#questions--answers&quot; aria-label=&quot;questions  answers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Questions &amp;#x26; Answers&lt;/h2&gt;
&lt;h3 id=&quot;partitioning&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#partitioning&quot; aria-label=&quot;partitioning permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Partitioning&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Michał:&lt;/strong&gt; &lt;em&gt;How many partitions is too much partitions? talking about weekly partitions for ~10 years?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If we follow the example from our webinar, the trips table looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; trips &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    trip_time TIMESTAMPTZ &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    vehicle_id &lt;span class=&quot;token keyword&quot;&gt;INT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    driver_name &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    start_location &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    end_location &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    distance_kilometers &lt;span class=&quot;token keyword&quot;&gt;NUMERIC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    fuel_used_liters &lt;span class=&quot;token keyword&quot;&gt;NUMERIC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;trip_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; vehicle_id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt; RANGE &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;trip_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- setup partitions for year&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;DO&lt;/span&gt; $$&lt;span class=&quot;token keyword&quot;&gt;DECLARE&lt;/span&gt;
    month_start_date &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt; :&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2023-01-01&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    month_end_date &lt;span class=&quot;token keyword&quot;&gt;DATE&lt;/span&gt; :&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2023-12-01&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;WHILE&lt;/span&gt; month_start_date &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; month_end_date &lt;span class=&quot;token keyword&quot;&gt;LOOP&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;EXECUTE&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;
            CREATE TABLE trips_%s PARTITION OF trips
            FOR VALUES FROM (%L) TO (%L);&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            TO_CHAR&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;month_start_date&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;YYYY_MM&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            month_start_date&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            month_start_date &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1 month&apos;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        month_start_date :&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; month_start_date &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTERVAL&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;1 month&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;LOOP&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;END&lt;/span&gt;$$&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, if we partition it by time, we’d get in 10 years:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;month - 120 partitions (12 * 10),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;week - 520 partitions (52 * 10),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;day - 3650 partitions(365 * 10).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously, 520 and 3650 sound like potentially a lot and should raise our eyebrows. PostgreSQL can handle up to a few thousand partitions fairly well, provided that typical queries allow the query planner to prune all but a small number of partitions. But here’s the catch - each partition requires its metadata to be loaded into the local memory of each session that touches it. See the nice research on how the number of partitions can impact performance: &lt;a href=&quot;https://pganalyze.com/blog/5mins-postgres-partitioning&quot;&gt;Partitioning in Postgres and the risk of high partition counts&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nevertheless, we may notice that for our case, partitions fall into three groups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;current partition&lt;/strong&gt; - where new trips are added,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;active period partitions&lt;/strong&gt; - e.g., the last 3 months, that are interesting for us from the alerting and reporting needs or maybe filling back missing trips,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;old partitions&lt;/strong&gt; - those that we don’t touch at all, or sporadically (e.g. older than 3 months).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This split is also essential from the perspective of another question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Serge:&lt;/strong&gt; &lt;em&gt;Is there a notion of hot partition?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, depending on our scenarios, some partitions may be the most used. That’s also why partition planning is critical, and it should be also made based on the business scenario. For our case, we should definitely care and keep focus on the current partition (the hot one, as it’s the most often used), and the active period.&lt;/p&gt;
&lt;p&gt;What about the rest? They can be moved either to cheaper, slower storage. PostgreSQL allows this through setting up a so-called &lt;em&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/manage-ag-tablespaces.html&quot;&gt;Tablespace&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We can do it by calling a SQL statement:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Create cheap storage&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLESPACE&lt;/span&gt; cold_storage LOCATION &lt;span class=&quot;token string&quot;&gt;&apos;/mnt/slow_disk&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- detach partion&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; trips DETACH &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; trips_2025_01&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- set cheaper disk for this table&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; trips_2025_01 &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLESPACE&lt;/span&gt; cold_storage&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We could also use the &lt;a href=&quot;https://github.com/pgpartman/pg_partman&quot;&gt;pg_partman&lt;/a&gt; extension to automate it:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Tell partman to keep only 2 years attached&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; partman&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;part_config
&lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; retention &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;3 months&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    retention_keep_table &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;-- detach but don&apos;t drop&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; parent_table &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;public.trips&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Run maintenance (or let background worker do it)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; partman&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run_maintenance&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That also answers the next question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Karol:&lt;/strong&gt; &lt;em&gt;How can we “move” older partitions to some less available/slower storage? Is available in postgres itself or some postgres cloud providers?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So it’s a native mechanism, but supported in the Cloud RDSes (also pg_partman is usually available there).&lt;/p&gt;
&lt;p&gt;Worth noting is that detaching means removing a partition from the parent table while keeping it as a standalone table. The detached partition continues to exist as a standalone table, but no longer has any ties to the table from which it was detached. &lt;a href=&quot;https://www.postgresql.org/docs/current/ddl-partitioning.html&quot;&gt;Read more in the partitioning docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can attach it again, if we want, or just delete the content or even the whole partition table if we don’t need to access it.&lt;/p&gt;
&lt;p&gt;That’s also important for the next question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Matteo:&lt;/strong&gt; &lt;em&gt;what about a time range query? like time &gt;= x AND time &amp;#x3C; y?. will Postgres touch only the relevant partitions?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, PostgreSQL uses partition pruning to automatically skip irrelevant partitions when you query with conditions on the partition key.&lt;/p&gt;
&lt;p&gt;Here’s how it works with our trips table when using the &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-explain.html&quot;&gt;Explain tool&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;EXPLAIN&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ANALYZE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; BUFFERS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; trips
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; trip_time &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2025-06-15&apos;&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; trip_time &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2025-07-15&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’ll get something close to:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Append  (cost=0.00..241.00 rows=4800 width=64) (actual time=0.015..0.467 rows=4752 loops=1)
  Subplans Removed: 10    -- ← 10 partitions skipped
  -&gt;  Seq Scan on trips_2025_06  (cost=0.00..48.20 rows=1200 width=64) (actual time=0.014..0.128 rows=1188 loops=1)
        Filter: ((trip_time &gt;= &apos;2025-06-15&apos;::timestamp) AND (trip_time &amp;lt; &apos;2025-07-15&apos;::timestamp))
  -&gt;  Seq Scan on trips_2025_07  (cost=0.00..48.20 rows=1200 width=64) (actual time=0.012..0.115 rows=1176 loops=1)
        Filter: ((trip_time &gt;= &apos;2025-06-15&apos;::timestamp) AND (trip_time &amp;lt; &apos;2025-07-15&apos;::timestamp))&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;PostgreSQL will only query (scan) partition tables &lt;em&gt;trips_2025_06&lt;/em&gt; and &lt;em&gt;trips_2025_07&lt;/em&gt; because our date range spans mid-June to mid-July. The other 10 monthly partitions were pruned away. Pruned means skipped.&lt;/p&gt;
&lt;p&gt;Two types of pruning occur:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Planning time pruning&lt;/strong&gt; - when query values are constants (like above)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Execution time pruning&lt;/strong&gt; - with &lt;a href=&quot;https://www.postgresql.org/docs/current/sql-prepare.html&quot;&gt;prepared statements&lt;/a&gt; or subqueries where values aren’t known until runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why partitioning by time works well for time-series data - PostgreSQL only touches partitions that overlap with your date range. Still, the recommendation is to try avoid doing queries between partitions. We should make our partitions as autonomous as possible, as querying between partitions is costly (may even mean accessing different disks, etc.).&lt;/p&gt;
&lt;p&gt;That’s also how we’re coming to the next two questions:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Barry:&lt;/strong&gt; &lt;em&gt;What happens when you run this on a table with data already in it? Will it automatically split the data into the relevant partition?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nikita:&lt;/strong&gt; &lt;em&gt;Please share your experience with rebalancing of partitions.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;PostgreSQL doesn’t have automatic partition rebalancing like distributed databases. Once data lands in a partition, it stays there unless you manually move it. This becomes a problem when your partitioning strategy no longer fits your data patterns.&lt;/p&gt;
&lt;p&gt;The most common issue? You chose the wrong partition size. Maybe you started with yearly partitions, but now queries are slow because each partition has too much data. You need to split them, e.g.:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Can&apos;t just alter boundaries - must detach and recreate&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; trips DETACH &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; trips_2025&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Create monthly partitions instead&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; trips_2025_01 &lt;span class=&quot;token keyword&quot;&gt;PARTITION&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;OF&lt;/span&gt; trips
&lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;2025-01-01&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TO&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;2025-02-01&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- ... create all 12 months&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Move data from the old yearly partition&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; trips &lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; trips_2025&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- drop old years partition&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;DROP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; trips_2025&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Another case is when you have data in the wrong partitions. Updating your partition key value won’t move your data to a different partition, so instead of updating the partition key value, you need to insert it into the new partition with the new key and drop it from the old one. You can do it as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WITH&lt;/span&gt; moved &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; trips_2024_12  
  &lt;span class=&quot;token comment&quot;&gt;-- condition to find record(s) you want to move&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; trip_time &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2024-12-31&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;and&lt;/span&gt; vehicle_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;RETURNING&lt;/span&gt;
    vehicle_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    driver_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    start_location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    end_location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    distance_kilometers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    fuel_used_liters
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; trips &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;trip_time&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; vehicle_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; driver_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; start_location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
                   end_location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; distance_kilometers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fuel_used_liters&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2025-01-01&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; driver_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; start_location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       end_location&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; distance_kilometers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fuel_used_liters
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; moved&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the same way, if you have a table that’s not yet partitioned but has existing data, then PostgreSQL will refuse to partition it. You have two options. Either manually create new partitioned tables and migrate data first, or use pg_partman, which can partition existing tables:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; partman&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create_parent&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;public.trips&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;trip_time&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;native&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;monthly&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CALL&lt;/span&gt; partman&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;partition_data_proc&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;public.trips&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So, again, PostgreSQL extensions ecosystem FTW!&lt;/p&gt;
&lt;p&gt;And final question on partitioning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Dillan&lt;/strong&gt;: &lt;em&gt;Does ef core support partitions&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not at the moment, you’ll need to set up partitions on your own through custom migration, and AFAIK, the same story for other popular ORMs. Still, that’s not a big issue, as when you do the setup, you can query a partitioned table as any other table.&lt;/p&gt;
&lt;h2 id=&quot;logical-replication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#logical-replication&quot; aria-label=&quot;logical replication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Logical Replication&lt;/h2&gt;
&lt;p&gt;I wrote in more detail about PostgreSQL Logical replication and Write-Ahead Log in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/push_based_outbox_pattern_with_postgres_logical_replication/&quot;&gt;Push-based Outbox Pattern with Postgres Logical Replication&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/the-write-ahead-log-a-foundation/&quot;&gt;The Write-Ahead Log: The underrated Reliability Foundation for Databases and Distributed systems&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check also a great article from Gunnar Morling, an authority in this space:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.infoq.com/articles/wonders-of-postgres-logical-decoding-messages/&quot;&gt;The Wonders of Postgres Logical Decoding Messages&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They should answer some of the questions. Let’s do a super quick TLDR of the key concepts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A Write-Ahead Log is a structure on the disk to which all statements representing changes (INSERT, UPDATE, DELETE)&lt;/strong&gt; are appended before applying them to tables upon transaction commit. Most of the databases are cleaning those entries, soon after they’re committed, PostgreSQL allows us to keep them as long as we tell it to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This enables Logical replication, which streams database changes from a publisher to subscribers using the Write-Ahead Log.&lt;/strong&gt; Unlike physical replication that copies everything byte-by-byte, logical replication sends actual SQL-like changes (thus logical, as they represent the logical change, not serialised bytes of data).&lt;/p&gt;
&lt;p&gt;Key components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Publication:&lt;/strong&gt; Defines which tables to replicate. You set it up by calling:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;CREATE PUBLICATION outbox_pub FOR TABLE outbo&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Replication Slot:&lt;/strong&gt; Tracks subscriber progress in WAL, ensures no data loss (pg_create_logical_replication_slot)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Subscriber:&lt;/strong&gt; Connects to slot, receives changes, sends acknowledgements&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, answering Matteo’s question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Matteo&lt;/strong&gt;: &lt;em&gt;How does logical replication relate to Change Data Capture?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Logical replication IS PostgreSQL’s native CDC. It captures changes from WAL and streams them out, the same as &lt;a href=&quot;https://debezium.io/&quot;&gt;Debezium&lt;/a&gt; or other CDC tools.&lt;/p&gt;
&lt;p&gt;The difference is scope. Debezium uses logical replication internally, providing a higher abstraction level and features such as connectors to Kafka. It handles batching, retries, and monitoring. With native logical replication, you build that plumbing yourself.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Muhammad:&lt;/strong&gt; I assume WAL notifications are ordered?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, strictly ordered by transaction commit order. WAL is append-only. PostgreSQL tracks subscriber progress through replication slots using LSN (Log Sequence Number - read more &lt;a href=&quot;https://www.crunchydata.com/blog/postgres-wal-files-and-sequuence-numbers&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Ordering is per replication slot.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Muhammad:&lt;/strong&gt; &lt;em&gt;What happens when there are multiple subscribers to WAL notifications?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Multiple subscribers of the same slot process at different speeds, so “ordered” messages might arrive out of order at different downstream systems. So it’s similar to RabbitMQ guarantees for multiple consumers of the same queue.&lt;/p&gt;
&lt;p&gt;If you need global ordering across systems, you need to either have a single subscriber for the replication slot or you need additional coordination.&lt;/p&gt;
&lt;p&gt;Read also more in &lt;a href=&quot;/en/the-order-of-things-why-you-cant/&quot;&gt;The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Muhammad:&lt;/strong&gt; &lt;em&gt;Do we get publisher confirms with this Postgres Publication&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not in the traditional message queue sense. The subscriber sends acknowledgements after processing batches of changes, not individual messages - that’s why we call SendStatusUpdate() in the code example.&lt;/p&gt;
&lt;p&gt;PostgreSQL gives you position-based tracking. If your subscriber crashes after processing message 100 but before sending the status update, it’ll reprocess from maybe message 80. Your consumer must be idempotent.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Muhammad:&lt;/strong&gt; &lt;em&gt;Does WAL get auto flushed if there is no subscriber confirms?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No. WAL segments stick around until the subscriber confirms. Replication slots will prevent the removal of required resources even when there is no connection. But that also brings cost:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nikita:&lt;/strong&gt; &lt;em&gt;Does WAL grow indefinitely when a subscriber goes down? Can we limit the growth?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes, indefinitely by default. To understand why, you need to know how WAL cleanup works.&lt;/p&gt;
&lt;p&gt;PostgreSQL continuously writes changes to WAL files in the pg_wal directory. Each file is 16MB by default. Without replication slots, PostgreSQL deletes old WAL files after:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;They’ve been checkpointed (data written to main tables)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They’ve been archived (if archiving is enabled)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s compare that to how messaging systems deal with retention.&lt;/p&gt;
&lt;p&gt;In Kafka, you set retention policies like “keep data for 7 days” or “keep 100GB”. Data gets deleted after this period, regardless of whether consumers read it. Late consumers simply miss old data.&lt;/p&gt;
&lt;p&gt;PostgreSQL works differently. WAL retention is controlled by replication slots, not publications. A publication defines which tables to replicate - it has no retention policy. The slot tracks the consumer’s position and prevents WAL deletion until consumed.&lt;/p&gt;
&lt;p&gt;With slots, PostgreSQL also checks every slot position before deletion. One slow slot blocks WAL cleanup for everyone.&lt;/p&gt;
&lt;p&gt;If you want Kafka-like behaviour where data is available for future subscribers, you need WAL archiving:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;-- Archive WAL to long-term storage
archive_mode = on
archive_command = &apos;cp %p /mnt/wal_archive/%f&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This copies WAL files before deletion. Future subscribers can replay from the archive, not the active WAL. But logical replication doesn’t automatically use archives - you’d need to restore them manually or use tools like &lt;a href=&quot;https://www.postgresql.org/docs/current/app-pgrewind.html&quot;&gt;pg_rewind&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also set the maximum size of data to keep for the slot:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ALTER SYSTEM SET max_slot_wal_keep_size = &apos;10GB&apos;;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This works like a &lt;a href=&quot;https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern&quot;&gt;circuit breaker&lt;/a&gt; - slots that retain too much WAL get invalidated rather than filling your disk. But unlike Kafka, where late consumers just miss data, PostgreSQL slots become permanently broken.&lt;/p&gt;
&lt;p&gt;The fundamental difference: Kafka decouples retention from consumption. PostgreSQL couples them tightly - data stays until consumed or the slot breaks. You can’t have “keep WAL for 7 days for future subscribers” without archiving.&lt;/p&gt;
&lt;p&gt;So again, just like with partitioning, archiving strategy is the key!&lt;/p&gt;
&lt;h2 id=&quot;trivia&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#trivia&quot; aria-label=&quot;trivia permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Trivia&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;What are core key features not present in SQL Server?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Lots to mention! Jokes aside, MSSQL is a decent database, but from my perspective, Microsoft is lagging behind and underfunding efforts on it. Even on their conferences, they announce more news around PostgreSQL than MSSQL. The biggest difference is in the ecosystem and extensions being first-class citizens.&lt;/p&gt;
&lt;p&gt;Still, here’s a quick list from the top of my head:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Extension system -&lt;/strong&gt; The ability to add functionality without touching core code. That’s how we get pg_partman, PostGIS, TimescaleDB. SQL Server has nothing comparable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Logical replication with custom output plugins -&lt;/strong&gt; SQL Server has CDC, but for traditional replication, you can’t customise or plug into it as described for PostgreSQL.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Partitioning in MSSQL is just for the enterprise version, and IMHO, more complicated than in PostgreSQL.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://wiki.postgresql.org/wiki/Foreign_data_wrappers&quot;&gt;Foreign Data Wrappers&lt;/a&gt; -&lt;/strong&gt; Query external databases, CSV files, APIs as if they were local tables. SQL Server has linked servers but they’re not as flexible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Custom types and operators&lt;/strong&gt; - Define your own data types and how they behave. SQL Server is limited to built-in types.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JSONB with GIN indexing -&lt;/strong&gt; While SQL Server has JSON support, PostgreSQL’s JSONB with indexing is more mature and performant.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tablespaces -&lt;/strong&gt; Place specific tables/partitions on different disks. SQL Server uses filegroups, but PostgreSQL’s approach is simpler for partitioning.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Arrays as first-class citizens&lt;/strong&gt; - you can store and index arrays directly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rich ecosystem of various hosting services, besides cloud RDSes there’s a variety of bare metal VPCs, providers like &lt;a href=&quot;https://supabase.com/&quot;&gt;Supabase&lt;/a&gt;, &lt;a href=&quot;https://neon.com/&quot;&gt;Neon&lt;/a&gt;. And cloud-native databases like &lt;a href=&quot;https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Aurora.AuroraPostgreSQL.html&quot;&gt;AWS AuroraDB&lt;/a&gt;, &lt;a href=&quot;https://cloud.google.com/spanner/docs/postgresql-interface&quot;&gt;Google Spanner&lt;/a&gt;, &lt;a href=&quot;https://www.cockroachlabs.com/docs/stable/&quot;&gt;CockroachDB&lt;/a&gt;, &lt;a href=&quot;https://www.yugabyte.com/&quot;&gt;Yugabyte&lt;/a&gt;, compatible with PostgreSQL syntax.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The PostgreSQL Docker image is lighter and much easier to run integration tests.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Peter:&lt;/strong&gt; &lt;em&gt;What misuse of PostgreSQL (feature or in general) has turned out to be useful?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’d say that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Using PostgreSQL as a document database -&lt;/strong&gt; I wrote about it in &lt;a href=&quot;/en/postgresql-jsonb-powerful-storage/&quot;&gt;PostgreSQL JSONB - Powerful Storage for Semi-Structured Data&lt;/a&gt;. What seemed to be a wild idea worked really well. For year, I was co-maintaining .NET tool &lt;a href=&quot;https://martendb.io/&quot;&gt;Marten&lt;/a&gt;, which allows using PostgreSQL as a document db, now I’m building also &lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt; - a Mongo-compliant alternative in Node.js. Also tools like FerretDB and even cloud providers used it, e.g. AWS in DocumentDB, Microsoft in CosmosDB.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Using PostgreSQL as a queue -&lt;/strong&gt; that’s not something I’d recommend as the safe default, but if throughput of a few thousand messages per second is fine for you, then why not? That’s what Particular did in &lt;a href=&quot;https://docs.particular.net/transports/postgresql/&quot;&gt;NServiceBus transport&lt;/a&gt;. There are popular tools like &lt;a href=&quot;https://supabase.com/docs/guides/queues&quot;&gt;Supabase queues&lt;/a&gt;, &lt;a href=&quot;https://github.com/timgit/pg-boss&quot;&gt;pgBoss&lt;/a&gt;, beware of &lt;em&gt;Just use PostgreSQL for all advice&lt;/em&gt;, as &lt;a href=&quot;/en/just-use-sql-they-say-or-on-how-accidental/&quot;&gt;Just use X is dangerous advice&lt;/a&gt;, but can be a good choice.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Using PostgreSQL as an event store&lt;/strong&gt; - that’s what I did in Marten, and do in &lt;a href=&quot;/en/emmett_postgresql_event_store/&quot;&gt;Emmett&lt;/a&gt;, and also other tools are doing. Don’t write your own, now you have tools in all popular environments that’ll do it for you.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Full-Text Search Engine&lt;/strong&gt; - Using PostgreSQL’s full-text search capabilities instead of Elasticsearch or Solr might not be always the best ide, but for many use cases, &lt;a href=&quot;https://www.postgresql.org/docs/current/textsearch.html&quot;&gt;PostgreSQL’s tsvector and tsquery&lt;/a&gt; are more than sufficient for many cases and can eliminate operational complexity if you’re already using PostgreSQL and don’t have sophisticated needs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Peter:&lt;/strong&gt; &lt;em&gt;What is a common thing you see as a consultant that many teams+orgs get wrong with postgres?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think that the issues are similar to those in other environments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;Not using or misusing connection pooling&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;not using explain and not tuning queries,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;not using prepared statements for the commonly used queries,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;not setting or oversetting indexes,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;not using optimistic concurrency,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;not thinking about the transaction isolation, and causing deadlocks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From the issues that are special for PostgreSQL:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/ordering_in_postgres_outbox/&quot;&gt;not knowing how PostgreSQL sequences works&lt;/a&gt;, and losing messages when using the outbox pattern or PostgreSQL as a message queue,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Not understanding VACUUM and autovacuum:&lt;/strong&gt; PostgreSQL’s MVCC model means dead tuples accumulate and need cleanup. Many teams either disable autovacuum (terrible idea) or don’t tune it properly for their workload. This leads to table bloat, degraded query performance. Check more about it in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.interdb.jp/pg/pgsql06.html&quot;&gt;Internals of PostgreSQL VACUUM Processing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7a1otYLZxy4&quot;&gt;Robert Haas: Understanding and Fixing Autovacuum&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Woof, that was a lot! I hope that this was useful and I didn’t mess anything! How would you answer those questions? Or maybe you have more of them?&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Just use SQL they say... Or how accidental complexity piles on]]></title><description><![CDATA[Welcome to the new week! “Just use SQL”, they say. “No need for ORMs”, they add. And they may be right, but… Let’s say that you want to…]]></description><link>https://event-driven.io/en/just-use-sql-they-say-or-on-how-accidental/</link><guid isPermaLink="false">https://event-driven.io/en/just-use-sql-they-say-or-on-how-accidental/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;“Just use SQL”&lt;/strong&gt;&lt;/em&gt;, they say.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;“No need for &lt;a href=&quot;https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping&quot;&gt;ORM&lt;/a&gt;s”&lt;/strong&gt;&lt;/em&gt;, they add.&lt;/p&gt;
&lt;p&gt;And they may be right, but…&lt;/p&gt;
&lt;p&gt;Let’s say that you want to query users. In TypeScript, this could look like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;SELECT userName, email FROM users&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Easy peasy!&lt;/p&gt;
&lt;p&gt;Now, let’s add a bit more, so filtering, let’s say that we’d like to find users with a specific e-mail.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;const email &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;oskar@event-driven.io&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
const &lt;span class=&quot;token keyword&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;
  SELECT userName, email
  FROM users
  WHERE email = &apos;${ email }&apos;
&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
const result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; await connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Still a piece of cake, right? We’re using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals&quot;&gt;Template Strings&lt;/a&gt;, a feature available nowadays in most languages (oh well, Java, hey! I’m looking at you!).&lt;/p&gt;
&lt;p&gt;This will work, but what if the email wasn’t the constant value, as I did, but was provided by the user? Well, then one could give an email with the value:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; email &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;; DROP TABLE users; SELECT &apos;drop da base!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;…or something similar, depending on your SQL dialect, and cause (in) famous &lt;a href=&quot;https://en.wikipedia.org/wiki/SQL_injection&quot;&gt;SQL Injection&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/0ddff8ef902142c00a92ad96ab4e9215/44838/2025-09-08-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECA//EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAGNMUkDI//EABsQAAICAwEAAAAAAAAAAAAAAAECABESEyMy/9oACAEBAAEFAlJUt4DrWJWdL1T/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAAQIf/aAAgBAgEBPwGH/8QAGRAAAgMBAAAAAAAAAAAAAAAAAAECETFB/9oACAEBAAY/Ah0cE40ZFGn/xAAbEAEAAgMBAQAAAAAAAAAAAAABABEhMVFxgf/aAAgBAQABPyEg3hME2CeXuOs09grJHYbvijzSf//aAAwDAQACAAMAAAAQkN//xAAXEQEBAQEAAAAAAAAAAAAAAAABEQBR/9oACAEDAQE/EG2jo93/xAAXEQEBAQEAAAAAAAAAAAAAAAABABFR/9oACAECAQE/EABjYcv/xAAcEAEAAgMAAwAAAAAAAAAAAAABABEhMVFxkaH/2gAIAQEAAT8QvAW3ADe/cCpSZSfjsHInE3MbbQDikhRKhuwteMxF0eAT/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/0ddff8ef902142c00a92ad96ab4e9215/507e6/2025-09-08-cover.webp 200w,
/static/0ddff8ef902142c00a92ad96ab4e9215/28a80/2025-09-08-cover.webp 400w,
/static/0ddff8ef902142c00a92ad96ab4e9215/8d2ea/2025-09-08-cover.webp 800w,
/static/0ddff8ef902142c00a92ad96ab4e9215/64908/2025-09-08-cover.webp 1080w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/0ddff8ef902142c00a92ad96ab4e9215/37402/2025-09-08-cover.jpg 200w,
/static/0ddff8ef902142c00a92ad96ab4e9215/4cda9/2025-09-08-cover.jpg 400w,
/static/0ddff8ef902142c00a92ad96ab4e9215/c60e9/2025-09-08-cover.jpg 800w,
/static/0ddff8ef902142c00a92ad96ab4e9215/44838/2025-09-08-cover.jpg 1080w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/0ddff8ef902142c00a92ad96ab4e9215/c60e9/2025-09-08-cover.jpg&quot;
            alt=&quot;Speed camera SQL Injection : r/geek&quot;
            title=&quot;Speed camera SQL Injection : r/geek&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;One solution would be to try to validate or sanitise the email value. Still, have you seen the e-mail regex? Not quite as easy as we’d like to: see &lt;a href=&quot;https://stackoverflow.com/a/201378&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Luckily, databases give us parameterised queries so that we can provide params that will be sanitised and mapped. For instance, like that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;const &lt;span class=&quot;token keyword&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;
  SELECT userName, email
  FROM users
  WHERE email = ?
&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
const result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; await connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cool, but let’s say that we’d like to have email, be just one of the params, and our filtering params look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UsersFilter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   userName&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   email&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filterUsers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UsersFilter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;SELECT userName, email FROM users&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userName &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     sql &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; WHERE &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

     &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; hasParams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

     &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       sql &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; userName = ?&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       hasParams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

     &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
       &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hasParams&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; AND &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

       sql &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; email = ?&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
       params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Boy, that escalated quickly…&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/aa05b34f284c7b49962beaf4904bbfe3/38b44/image-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwQA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAYGhoCxaP//EABkQAAMBAQEAAAAAAAAAAAAAAAECAwAyM//aAAgBAQABBQKVjPVcsHOXU9Kd/wD/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPwGH/8QAGxAAAgEFAAAAAAAAAAAAAAAAAAEQAhEhMXH/2gAIAQEABj8CwXq2LkJR/8QAGRABAAMBAQAAAAAAAAAAAAAAAQARIRAx/9oACAEBAAE/IQtttQ1VbOVhExmkeWRqp//aAAwDAQACAAMAAAAQTz//xAAYEQEBAAMAAAAAAAAAAAAAAAABABEhMf/aAAgBAwEBPxAcup7f/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARIf/aAAgBAgEBPxBwdgv/xAAcEAEBAQABBQAAAAAAAAAAAAABEQAhMUFRkbH/2gAIAQEAAT8QdjCAVhMzRkpy0+dsCjxD7clvAImIcOJJNUHpA3//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/aa05b34f284c7b49962beaf4904bbfe3/507e6/image-2.webp 200w,
/static/aa05b34f284c7b49962beaf4904bbfe3/28a80/image-2.webp 400w,
/static/aa05b34f284c7b49962beaf4904bbfe3/8d2ea/image-2.webp 800w,
/static/aa05b34f284c7b49962beaf4904bbfe3/68fc1/image-2.webp 1200w,
/static/aa05b34f284c7b49962beaf4904bbfe3/4b075/image-2.webp 1280w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/aa05b34f284c7b49962beaf4904bbfe3/37402/image-2.jpg 200w,
/static/aa05b34f284c7b49962beaf4904bbfe3/4cda9/image-2.jpg 400w,
/static/aa05b34f284c7b49962beaf4904bbfe3/c60e9/image-2.jpg 800w,
/static/aa05b34f284c7b49962beaf4904bbfe3/6c738/image-2.jpg 1200w,
/static/aa05b34f284c7b49962beaf4904bbfe3/38b44/image-2.jpg 1280w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/aa05b34f284c7b49962beaf4904bbfe3/c60e9/image-2.jpg&quot;
            alt=&quot;Boy, that escalated quickly. I mean, that really got out of hand fast.&quot;
            title=&quot;Boy, that escalated quickly. I mean, that really got out of hand fast.&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;And that’s just basic filtering of two parameters. What if there were 10? What if there were even more?&lt;/p&gt;
&lt;p&gt;Of course, we could make it more generic, more sneaky, but I’m sure that you’re starting to get the idea where we’re getting into.&lt;/p&gt;
&lt;p&gt;We’re getting into the &lt;em&gt;&lt;a href=&quot;https://wiki.c2.com/?StringlyTyped&quot;&gt;Stringly Typed&lt;/a&gt;&lt;/em&gt; world where we’re doing complicated string manipulations. We not only need to generate SQL based on the provided criteria in a predictable manner, but also ensure that the parameters in the SQL query match the values provided in the parameters array when querying values.&lt;/p&gt;
&lt;p&gt;What can we do about it? Let’s try to simplify the filtering query generation first.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ParametrizedQuery&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  sql&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  params&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; emptyQuery&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ParametrizedQuery &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sql&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re benefiting from the dynamic nature of the JS/TS here: each object is a record with field names and values. In other environments, we’d need to use either Reflection or code generation. Still, we find the field names,&lt;/p&gt;
&lt;p&gt;Cool, now we can use it in our method:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filterUsers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UsersFilter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;SELECT userName, email FROM users&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sql&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; whereSql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; whereSql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That solved the issue for some time, but… What if we’d like to support not only equals but also: greater than, less than, LIKE, etc.?&lt;/p&gt;
&lt;p&gt;Then we’d need to try harder!&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ColumnOperator&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;eq&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;like&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;gt&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;lt&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;gte&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;lte&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ColumnFilter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  columnName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;
  operator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ColumnOperator&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WhereOperator&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AND&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;OR&apos;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;WhereFilter&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   columnFilters&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ColumnFilter&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   operator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WhereOperator&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We could define a column filter to contain the column name, value and operator. Then, the Where filter would be a collection of column filters and a binary operator.&lt;/p&gt;
&lt;p&gt;We could map our filter object to the WhereFilter using:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BuildWhereFilterOptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
   map&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;property&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; ColumnFilter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
   operator&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WhereOperator
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildWhereFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  filter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Record&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; BuildWhereFilterOptions
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WhereFilter &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; map&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; operator &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; columnFilters &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Object&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; value &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      columnName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; prop&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      operator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;eq&apos;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    columnFilters&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    operator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; operator &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AND&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Besides the filter, we’re also allowing an optional pass mapping function to customise filtering and also the general binary operator.&lt;/p&gt;
&lt;p&gt;We could use it in the where SQL generation:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;mapToSQL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;columnFilter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ColumnFilter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; columnName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; operator &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; columnFilter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;operator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;eq&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;columnName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; = ?&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;like&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;columnName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; LIKE ?&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;gt&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;columnName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &gt; ?&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;lt&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;columnName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &amp;lt; ?&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;gte&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;columnName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &gt;= ?&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;lte&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;columnName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &amp;lt;= ?&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;columnName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; = ?&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WhereFilter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ParametrizedQuery &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; columnFilters&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; filters&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; operator &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; filter&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; columnFilters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      sql&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos; WHERE &apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; filters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;mapToSQL&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;operator&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
      params&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; filters&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;c &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; c&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; emptyQuery&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And now we can customise our filtering, adding LIKE for user name with:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;filterUsers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;usersFilter&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; UsersFilter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sql &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;SELECT userName, email FROM users&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildWhereFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;usersFilter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prop&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prop &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;userName&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          columnName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; prop&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          value&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;value&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          operator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;like&apos;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sql&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; whereSql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;whereFilter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;execute&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sql &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; whereSql&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And that’s just a beginning! As we see, we have &lt;a href=&quot;/en/compilation-isnt-just-for-programming/&quot;&gt;almost built a simple WHERE condition compiler&lt;/a&gt;! Or at least we’re not that far from it.&lt;/p&gt;
&lt;p&gt;Where am I going with this? Why am I showing you such code?&lt;/p&gt;
&lt;p&gt;Definitely &lt;strong&gt;NOT&lt;/strong&gt; to tell you “just use ORM!”.&lt;/p&gt;
&lt;p&gt;That’s not the point here. SQL is fine, ORMs are fine. When used in the proper context.&lt;/p&gt;
&lt;p&gt;One of the reasons is that I’m working on such stuff in &lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt;, adding multiple database support and some sneaky SQL handling. I don’t want to be alone with that!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The other, and most important, story behind it is to show you that it’s never “just”.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Just is enough for a simple or explicit context. Eventually, as our system grows, we’ll need to deal with complications. Popular tools might not be perfect; they may be too heavy, depending on design decisions, but as Gerald Weinberg said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Things&lt;/em&gt; are &lt;em&gt;the way they&lt;/em&gt; are &lt;em&gt;because they&lt;/em&gt; got that &lt;em&gt;way&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can live “just” with something. This can be a good starting point. Yet, we should always consider when deciding on DIY or “take off the shelf” solutions, where we need to land and what our main problem is to solve.&lt;/p&gt;
&lt;p&gt;Because if it appears that we’ll need more advanced features, then we’ll follow the path similar to the one that people building popular libraries/tools have taken.&lt;/p&gt;
&lt;p&gt;It’s always worth thinking whether “just” will be enough for us. Usually, “just” is enough only for some time. And it is better not to overlook this to change our approach, as then we can deal with accidental complexity related to wrongly weighted “just” as described in this article.&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how_to_solve_complicated_problems/&quot;&gt;Not all issues are complex, some are complicated. Here’s how to deal with them&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/in_the_defence_of_orms/&quot;&gt;In the defence of Object-Relational Mappers&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[My thoughts on Vertical Slices, CQRS, Semantic Diffusion and other fancy words]]></title><description><![CDATA[Welcome to the new week! Vertical Slices in software architecture are pictured right now as the best thing since sliced bread. I won’t try…]]></description><link>https://event-driven.io/en/my-thoughts-on-vertical-slices-cqrs/</link><guid isPermaLink="false">https://event-driven.io/en/my-thoughts-on-vertical-slices-cqrs/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;Vertical Slices in software architecture are pictured right now as the best thing since sliced bread. I won’t try to hide that, like it. I’ve written about CQRS and Vertical Slices over the years - &lt;a href=&quot;/en/how_to_slice_the_codebase_effectively/&quot;&gt;how to slice the codebase effectively&lt;/a&gt;, shown &lt;a href=&quot;/en/vertical_slices_in_practice/&quot;&gt;examples&lt;/a&gt;, and explained why &lt;a href=&quot;/en/generic_does_not_mean_simple/&quot;&gt;generic doesn’t mean simple&lt;/a&gt;, yet…&lt;/p&gt;
&lt;p&gt;I still get questions about Vertical Slices Architecture (VSA). After a recent Discord discussion, I want to share some additional thoughts on how I see Vertical Slices Architecture, how it relates to CQRS, what different slicing strategies are, and (of course) the tradeoffs.&lt;/p&gt;
&lt;h2 id=&quot;what-vertical-slices-architecture-actually-is&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-vertical-slices-architecture-actually-is&quot; aria-label=&quot;what vertical slices architecture actually is permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What Vertical Slices Architecture actually is&lt;/h2&gt;
&lt;p&gt;Funnily, many people using the Vertical Slices term don’t even know who coined it. Jimmy Bogard did it. In the &lt;a href=&quot;https://jimmybogard.com/vertical-slice-architecture/&quot;&gt;original article&lt;/a&gt; contrasts traditional layered architecture with vertical organisation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“In this style, my architecture is built around distinct requests, encapsulating and grouping all concerns from front-end to back. You take a normal “n-tier” or hexagonal/whatever architecture and remove the gates and barriers across those layers, and couple along the axis of change.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He showed the famous image:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 581px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7acdc75ab637432b6e5c6d2c8f61ffb4/daafc/2025-08-25-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 64.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABcRAAAXEQHKJvM/AAAC+ElEQVR42m3T609SYRwH8POf9DZ75+VFtZlJAsdzBAm8IYpHOHIJFYa3aSCX5myVlxBliAqC2hs1Xa5NQETnBV1O29rqT6gt8SDJkJJfh7OZwtrZZ8+z73fn9+Z5HuTAinvBxYEfw6xLe1dL4x11OK9cM1XE0vgKrhUSMwX3FTMFt7NcJZrZIlab7x7iseoBnI/gzI5Cq9kDmDkMT81+EJgDjEpTAMjhbWh6FQZ+/01+W6XZfyUa2Aa+4eNXZMaqT2UGRsdQUFm8F08su+eYZSNebgnFsRdbcdQSjktHI/H6ob04l8nCTJcjhlnDv1Gj/wj5ZMLXwFMOMRsnNf1MYlRW91Z11esa9WK9pA1XMmQcUkKipKSNr2Rkuts66+lP3CnViXU85KSdlbpylUHsbRns1LGTmzh6scnjJsKVWOJA0ZyIyKWJQ5U8ESGbaERiVypObPLQXL/C/PJ0oIJ7ghxpSv+kl0g4d4pgu4Z9GcR4yY0KPBni85MH7drkUW8fsx7q9Ix9pYruK7IEcZweyoN1DPuMfLEUfwB/L5xPVaXe64stI1pM7NRyZA4tl7CTD4kRWUEWO1lMZLpcE1pUZtNzapEtB+c7LMiAcmDp+WVBfDwkPnMG66iJQC01tdlEze/qKN9OGzVHy+ydGxLKQfdZAnWUMySmRlZFMXog+xTGy4Cys9MLywJwBGvB5a8B53o1TAcbYHGvAxb3O2Ap0kmvnXRXC5N0n2W9Bqbo/2yrQkCOB9ElcGP0KbNTXqOwW93XivcayKpug0LYYZALtd31QrlaICQUPGafyf+nx6gSdRhlOLLW1ZIE12M4s3HTNuWbWJ/03amJmI32E16GsckbHe1eidr61qIGqfdfns3300T4Es+l7giy1qNIg6sUzumLbdO8BgM5B5YWN5hbPAwT6YYx4zKMm1bAJL/Js5CeqwH1AhibXd+Q437uLEzSb3mIdelT1zVUlLjvtgushSr+YP410YOefFFRT/7tLJdaMFioFrzM+wt0pEPMKvW+agAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/7acdc75ab637432b6e5c6d2c8f61ffb4/507e6/2025-08-25-cover.webp 200w,
/static/7acdc75ab637432b6e5c6d2c8f61ffb4/28a80/2025-08-25-cover.webp 400w,
/static/7acdc75ab637432b6e5c6d2c8f61ffb4/82fb8/2025-08-25-cover.webp 581w&quot;
              sizes=&quot;(max-width: 581px) 100vw, 581px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/7acdc75ab637432b6e5c6d2c8f61ffb4/36ca5/2025-08-25-cover.png 200w,
/static/7acdc75ab637432b6e5c6d2c8f61ffb4/a3397/2025-08-25-cover.png 400w,
/static/7acdc75ab637432b6e5c6d2c8f61ffb4/daafc/2025-08-25-cover.png 581w&quot;
            sizes=&quot;(max-width: 581px) 100vw, 581px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/7acdc75ab637432b6e5c6d2c8f61ffb4/daafc/2025-08-25-cover.png&quot;
            alt=&quot;2025 08 25 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;When you add or change a feature, you’re touching many different layers - the UI, the models, the validation, the data access. Instead of spreading these changes across multiple horizontal layers, you could group everything related to that feature. As Jimmy puts it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Minimize coupling between slices, and maximize coupling in a slice.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So Instead of:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 controllers
  📄 ProductController.cs
  📄 OrderController.cs
📁 services  
  📄 ProductService.cs
  📄 OrderService.cs
📁 repositories
  📄 ProductRepository.cs
  📄 OrderRepository.cs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You organise more or less like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 ecommerce
  📁 products
    📁 create-product
      📄 createProduct.cs
      📄 createProductHandler.cs
    📁 update-product
      📄 updateProduct.cs  
      📄 updateProductHandler.cs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each vertical slice can decide for itself how to best fulfil the request. Simple CRUD? Use a transaction script. Complex domain logic? Bring in aggregates and domain services. The architecture doesn’t force a one-size-fits-all approach.&lt;/p&gt;
&lt;h2 id=&quot;why-i-think-vsa-is-rebranded-cqrs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-i-think-vsa-is-rebranded-cqrs&quot; aria-label=&quot;why i think vsa is rebranded cqrs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why I think VSA is rebranded CQRS&lt;/h2&gt;
&lt;p&gt;For me, Vertical Slices Architecture is essentially CQRS with more prescriptive guidance on how to cut your architecture.&lt;/p&gt;
&lt;p&gt;Let’s look at what CQRS actually says. &lt;a href=&quot;https://cqrs.wordpress.com/wp-content/uploads/2010/11/cqrs_documents.pdf&quot;&gt;Greg Young’s original definition&lt;/a&gt; is simple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;”&lt;strong&gt;Command and Query Responsibility&lt;/strong&gt; Segregation uses the same definition of Commands and Queries that Meyer used and maintains the viewpoint that they should be pure. The fundamental difference is that in CQRS objects are split into two objects, one containing the Commands one containing the Queries.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ok, well, it’s simple but can be confusing. What are those “objects”? Data structures? Tables? Nay, they’re services that are handling specific business requests. We should segregate handling into commands and queries. Commands run business logic, and queries return data without causing side effects (so no-no to state change).&lt;/p&gt;
&lt;p&gt;Essentially, CQRS is an architecture pattern explaining how to structure our application based on the behaviour. (Read more of my thoughts in &lt;a href=&quot;/en/cqrs_facts_and_myths_explained/&quot;&gt;CQRS facts and myths explained&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;And hey, wait! Isn’t that already a vertical slice? Each command or query handler contains everything needed to process that specific request. The handler might talk directly to the database, or use domain objects, or call external services. The choice is encapsulated within that slice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The difference is that VSA is more explicit about this organisational principle and extends it beyond just the command/query split.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It says: Don’t just separate commands and queries, organise your entire codebase this way. Put the API endpoints, the validation, the business logic, and even the data access for a specific feature all in one place.&lt;/p&gt;
&lt;p&gt;In my experience, when you properly apply CQRS, you naturally drift towards Vertical Slices. The patterns reinforce each other. CQRS gives you the conceptual split (commands vs queries), and VSA gives you the organisational principle (group by feature, not by technical concern).&lt;/p&gt;
&lt;h2 id=&quot;two-approaches-to-slicing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#two-approaches-to-slicing&quot; aria-label=&quot;two approaches to slicing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Two approaches to slicing&lt;/h2&gt;
&lt;p&gt;During our Discord discussion, I found myself drawing diagrams to explain two different approaches I’ve seen teams use successfully. Let me share them with concrete examples.&lt;/p&gt;
&lt;h3 id=&quot;approach-1-pure-vertical-slices&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#approach-1-pure-vertical-slices&quot; aria-label=&quot;approach 1 pure vertical slices permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Approach 1: Pure vertical slices&lt;/h3&gt;
&lt;p&gt;In this approach, each slice is completely self-contained:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 room-reservations
  📁 reserving-room
    📄 reserveRoomEndpoint.ts    // ⬅️ API endpoint definition
    📄 reserveRoom.ts            // ⬅️ Business logic
  📁 confirming-reservation
    📄 confirmReservationEndpoint.ts
    📄 confirmReservation.ts
  📁 cancelling-reservation
    📄 cancelReservationEndpoint.ts
    📄 cancelReservation.ts&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The endpoint file handles HTTP concerns and application logic:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/api/reservations/:id/confirm&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reservation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reservationRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; confirmedReservation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;confirmReservation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      reservation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;mapToCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reservationRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;confirmedReservation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The business logic is pure:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;confirmReservation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  reservation&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Reservation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConfirmReservation
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; confirmedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; confirmedBy &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reservation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;pending&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Only pending reservations can be confirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// some other business logic if needed&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;reservation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;confirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    confirmedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    confirmedBy&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    requiresPayment&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;daysBetween&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reservation&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;from&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Splitting the endpoint from business logic gives you:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt; - You can test business logic without any dependencies. Just pass data in, assert on the result.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reusability&lt;/strong&gt; - The same business logic can be triggered from different sources (HTTP, message queue, CLI).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Focus&lt;/strong&gt; - The endpoint deals with infrastructure (database, external services). The handler deals with business rules.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Evolution&lt;/strong&gt; - When business logic grows complex, it’s already separated and ready to expand.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you need to extract this into a microservice, you copy the folder. Everything related to confirming reservations is in one place.&lt;/p&gt;
&lt;h3 id=&quot;approach-2-slices-with-a-thin-coordination-layer&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#approach-2-slices-with-a-thin-coordination-layer&quot; aria-label=&quot;approach 2 slices with a thin coordination layer permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Approach 2: Slices with a thin coordination layer&lt;/h3&gt;
&lt;p&gt;Sometimes you need some coordination - maybe all your reservation endpoints share authentication logic, or you want consistent URL patterns. Or maybe you’re a C# or Java developer, and taking away controllers is as hard (or harder) than taking your family away from you:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/556021d5b034559362c168cec58fd2c8/10bbb/image-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 150.99999999999997%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAeABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBP/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAAB3ugmUuUgtzecuOf/xAAbEAACAwEBAQAAAAAAAAAAAAABAgADERIUMv/aAAgBAQABBQI4FIE+Z2BAylmUM1fINqaiLg853ghFref/xAAYEQADAQEAAAAAAAAAAAAAAAAAAREQIf/aAAgBAwEBPwHmRIh//8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAHRAAAgICAwEAAAAAAAAAAAAAABEBIQIxEBJRgf/aAAgBAQAGPwLtOiIWyYYqLyHjNE5O2h+cTYn9Nn//xAAcEAEAAwADAQEAAAAAAAAAAAABABExIUFRYaH/2gAIAQEAAT8hwktVwjycWdSlWN9lB/amWgvvMaDi2S2A8nId9Efl75msQVyZlePaoQSqGXP/2gAMAwEAAgADAAAAEIzkwf/EABgRAQEBAQEAAAAAAAAAAAAAAAEAIREx/9oACAEDAQE/EB48glCDlnO3/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARUf/aAAgBAgEBPxBHspLuMut//8QAHhABAQADAAIDAQAAAAAAAAAAAREAITFBUWFxkYH/2gAIAQEAAT8QdLvD9rndKVwktW+cKoBo1shgyLLs7B+8qFPcNPWuY8Gpq8f3DYXiQbdt+Ma1sFQFJGT+fmCVByPRDBWcZ68s6ZVFQp2vTfMQjyLqQ+M//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/556021d5b034559362c168cec58fd2c8/507e6/image-2.webp 200w,
/static/556021d5b034559362c168cec58fd2c8/28a80/image-2.webp 400w,
/static/556021d5b034559362c168cec58fd2c8/10636/image-2.webp 500w&quot;
              sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/556021d5b034559362c168cec58fd2c8/37402/image-2.jpg 200w,
/static/556021d5b034559362c168cec58fd2c8/4cda9/image-2.jpg 400w,
/static/556021d5b034559362c168cec58fd2c8/10bbb/image-2.jpg 500w&quot;
            sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/556021d5b034559362c168cec58fd2c8/10bbb/image-2.jpg&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In this case, you might have:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 room-reservations
  📁 api
    📄 reservationsController.ts // ⬅️ Thin routing layer
  📁 reserving-room
    📄 reserveRoom.ts            // ⬅️ Business logic
  📁 confirming-reservation
    📄 confirmReservation.ts
  📁 cancelling-reservation
    📄 cancelReservation.ts&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The routes file acts as a facade, handling HTTP concerns and orchestration:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;reservationController&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;app&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Express&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/api/reservations&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; authenticate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;/api/reservations/:id/confirm&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reservation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reservationRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; confirmedReservation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;confirmReservation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        reservation&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;mapToCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; reservationRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;save&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;confirmedReservation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Other endpoints follow similar pattern&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The business logic remains pure in each slice. The facade is purely about the entry point for the application logic and HTTP handling, while business logic stays in individual slices.&lt;/p&gt;
&lt;p&gt;This facade serves several purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It provides a consistent URL structure for all reservation operations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It handles cross-cutting concerns like authentication in one place&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It maps HTTP concerns (status codes, headers) away from business logic&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It makes it easier to see all available operations at a glance&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;But notice - the facade is thin. It’s not a “service layer” with business logic.&lt;/strong&gt; It’s purely about HTTP-to-domain translation. The actual business logic remains in the vertical slices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Both approaches have tradeoffs.&lt;/strong&gt; The pure slices approach gives you maximum independence - each feature is truly isolated. But you might end up duplicating cross-cutting concerns like authentication or error handling. The facade approach gives you a central place to handle these concerns, but now you have coupling between the facade and the slices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Neither approach solves all problems.&lt;/strong&gt; With pure slices, you risk turning into a ”&lt;a href=&quot;https://medium.com/nick-tune-tech-strategy-blog/beyond-problem-and-solution-space-better-models-for-modern-product-development-4ad3d5d5cdc4&quot;&gt;feature factory&lt;/a&gt;” where each feature is built in isolation without considering the overall system design. You might lose sight of the broader domain concepts because everything is scattered across independent slices. With the facade approach, you risk the facade growing into a bloated orchestration layer that knows too much about the domain.&lt;/p&gt;
&lt;p&gt;The choice depends on your context. If you’re building a system where features are truly independent (maybe different teams own different features), pure slices work well. If you need consistency across related operations, a thin facade can help without sacrificing the benefits of vertical organisation.&lt;/p&gt;
&lt;p&gt;Jimmy Bogard actually goes further than just backend organisation - he suggests that slices can and should include the UI. A vertical slice can go from the user interface all the way to the database. If the goal is to minimise coupling between features, why stop at the API layer? But the difference in tooling, team skill sets between backend and frontend often makes this challenging, which is why many teams slice vertically on the backend while keeping a more traditional organisation on the frontend.&lt;/p&gt;
&lt;h2 id=&quot;crud-and-cqrs-are-orthogonal&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#crud-and-cqrs-are-orthogonal&quot; aria-label=&quot;crud and cqrs are orthogonal permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;CRUD and CQRS are orthogonal&lt;/h2&gt;
&lt;p&gt;People often ask if VSA or CQRS make sense for CRUD systems. They do. Your commands might be &lt;em&gt;OnboardUser, UpdateProductDefinition, and CancelSubscription&lt;/em&gt;, but behind the scenes, they could be a typical Create, Update, and Delete implemented with Object-Relational Mapper. Naming is a key thing, as it already establishes a business process behind this action.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Neither CQRS nor VSA require complex domain logic.&lt;/strong&gt; They’re structural patterns that work for simple scenarios, too.&lt;/p&gt;
&lt;p&gt;I’ve heard dozens of times that CRUD is simpler, and CQRS and VSA bring additional overhead. And I just can’t objectively see that if we follow the basic principles. Why is naming things based on the business process an overhead? Why does reflecting this flow explicitly in the code add additional work?&lt;/p&gt;
&lt;p&gt;I understand that requires thinking about what we’re doing, asking business and ourselves: “What is the feature that we want to deliver?”. Still, from my perspective, that’s the thing we’re brought to the game. Building a model to solve the specific business problem. That starts with the naming and the structure.&lt;/p&gt;
&lt;p&gt;If we name things closer to the business&lt;a href=&quot;https://www.youtube.com/watch?v=yV97QwC5gnE&quot;&gt;,&lt;/a&gt; we’re &lt;a href=&quot;https://www.youtube.com/watch?v=yV97QwC5gnE&quot;&gt;giving yourself more options for future evolution&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At the beginning, things look simple, but when requirements come and the system evolves, quite often the system stops looking like a simple CRUD.&lt;/p&gt;
&lt;p&gt;Vertical Slices can help in that, as you already established the split based on the features, start to think about them, but you can keep the implementation simple and evolve when you learn more about your domain.&lt;/p&gt;
&lt;h2 id=&quot;feature-folders-and-nested-modules&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#feature-folders-and-nested-modules&quot; aria-label=&quot;feature folders and nested modules permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Feature folders and nested modules&lt;/h2&gt;
&lt;p&gt;Speaking about organisation, we should not mistake folder layout with software architecture, but it’s also good if your code reflects the emphasis you make in your design.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As mentioned, Vertical Slices are focused on the specific business flow, yet… Beyond individual features, you need to organise at a higher level.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The approaches I’ve shown so far work well for individual features, but what happens when your system grows? How do you organise dozens or hundreds of features without ending up with a flat, unmanageable structure?&lt;/p&gt;
&lt;p&gt;Kamil Grzybek wrote an excellent article about &lt;a href=&quot;https://www.kamilgrzybek.com/blog/posts/feature-folders&quot;&gt;Feature Folders&lt;/a&gt;, where he contrasts technical folders (Controllers, Services, Repositories) with business-focused organisation. His key insight is about cohesion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Elements that change together should live together. This principle directly reduces cognitive load.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When you’re working on a reservation feature, you don’t need to keep a mental map of where the controller lives, where the service is, or where the repository might be. Everything is in the ReservingRoom folder. As Kamil puts it, this creates high cohesion because &lt;em&gt;“the elements inside a module belong together.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In practice, this often means nesting your vertical slices within larger business modules:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;📁 reservations
  📁 room-reservations
    📄 api.ts                    // ⬅️ Public API of room reservations
    📁 reserving-room
      📄 reserveRoom.ts
      📄 reserveRoomEndpoint.ts
    📁 confirming-reservation
      📄 confirmReservation.ts
      📄 confirmReservationEndpoint.ts
    📁 cancelling-reservation
      📄 cancelReservation.ts
      📄 cancelReservationEndpoint.ts
  📁 group-reservations
    📄 api.ts                    // ⬅️ Public API of group reservations
    📁 creating-group-reservation
      📄 createGroupReservation.ts
      📄 createGroupReservationEndpoint.ts
    📁 assigning-rooms-to-group
      📄 assignRoomsToGroup.ts
      📄 assignRoomsEndpoint.ts
📁 billing
  📄 api.ts                      // ⬅️ Public API of billing module
  📁 invoicing
    📄 api.ts                    // ⬅️ Public API of invoicing
    📁 generating-invoice
      📄 generateInvoice.ts
      📄 generateInvoiceEndpoint.ts
    📁 sending-invoice
      📄 sendInvoice.ts
      📄 sendInvoiceEndpoint.ts
  📁 payments
    📄 api.ts                    // ⬅️ Public API of payments
    📁 recording-payment
      📄 recordPayment.ts
      📄 recordPaymentEndpoint.ts
    📁 processing-refund
      📄 processRefund.ts
      📄 processRefundEndpoint.ts&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This creates a hierarchy where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Top-level folders represent major business areas (reservations, billing)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Second-level folders represent subdomains or aggregates (room-reservations, invoicing)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leaf folders represent specific use cases (reserving-room, generating-invoice)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;em&gt;api.ts&lt;/em&gt; files act as module boundaries, explicitly defining what’s public and what module is exposing and what features it has.&lt;/p&gt;
&lt;p&gt;This gives you encapsulation and focuses on the responsibility. Other modules can only use what you explicitly expose. When modules need to communicate, they do so through their public APIs - though you need to be careful about coupling. Direct synchronous calls between modules can create distributed transaction problems.&lt;/p&gt;
&lt;p&gt;This gives you encapsulation without requiring separate packages or complex dependency management. Other modules can only use what you explicitly expose. Thus, the facade pattern still has its point with Vertical Slices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Such an approach scales naturally.&lt;/strong&gt; When you’re working on invoicing, you’re in the Billing/Invoicing folder. Everything related to invoicing is there. You don’t need to jump between technical layers. The reduced cognitive load is dramatic - instead of thinking “where do I put this validation?” you think “I’m working on generating invoices, so it goes in GeneratingInvoice.”&lt;/p&gt;
&lt;p&gt;This also aligns with team organisation. The billing team owns the Billing folder. The reservations team owns the Reservations folder. Conway’s Law works in your favour instead of against you.&lt;/p&gt;
&lt;h2 id=&quot;starting-simple&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#starting-simple&quot; aria-label=&quot;starting simple permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Starting simple&lt;/h2&gt;
&lt;p&gt;The initial structure matters less than your ability to evolve it.&lt;/p&gt;
&lt;p&gt;Start simple. If you’re building a proof of concept, don’t create elaborate folder hierarchies. But structure your code so that evolution is possible. That’s why I like CQRS even for simple systems - it gives you a place to put complexity when it inevitably arrives.&lt;/p&gt;
&lt;p&gt;I’ve seen teams start with pure vertical slices and later add a coordination layer once they’ve identified patterns. I’ve seen the reverse, too - starting with controllers and gradually pushing logic into focused handlers. Both paths work because the team could evolve the architecture as they learned more about their domain.&lt;/p&gt;
&lt;p&gt;The anti-pattern is the “big design up front” where you create elaborate abstractions for problems you don’t have yet. Start with the simplest thing that could possibly work, but keep the path open for evolution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;From my experience, slicing your code by features pays off much better and reduces the range of change than artificial cross-cutting splits by technical aspects.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ok, so why is CQRS and Vertical Slices pictured as such complex beasts?&lt;/p&gt;
&lt;h2 id=&quot;semantic-diffusion-how-good-ideas-get-corrupted&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#semantic-diffusion-how-good-ideas-get-corrupted&quot; aria-label=&quot;semantic diffusion how good ideas get corrupted permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Semantic Diffusion: How good ideas get corrupted&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://martinfowler.com/bliki/SemanticDiffusion.html&quot;&gt;Martin Fowler coined the term&lt;/a&gt; &lt;em&gt;&lt;a href=&quot;https://martinfowler.com/bliki/SemanticDiffusion.html&quot;&gt;Semantic Diffusion&lt;/a&gt;&lt;/em&gt; &lt;a href=&quot;https://martinfowler.com/bliki/SemanticDiffusion.html&quot;&gt;back in 2006&lt;/a&gt;, describing what happens when:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“you have a word that is coined by a person or group, often with a pretty good definition, but then gets spread through the wider community in a way that weakens that definition.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He compared it to the children’s game where a message gets passed from person to person - each retelling adds distortions until the original meaning is lost. Popular terms suffer from this the most because they create longer chains of reinterpretation.&lt;/p&gt;
&lt;h3 id=&quot;how-cqrs-got-diffused&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-cqrs-got-diffused&quot; aria-label=&quot;how cqrs got diffused permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How CQRS got diffused&lt;/h3&gt;
&lt;p&gt;CQRS is a perfect example of semantic diffusion in action. Greg Young’s original definition was crystal clear: separate your objects into commands and queries. That’s the entire pattern.&lt;/p&gt;
&lt;p&gt;Somewhere along the way, CQRS accumulated baggage. Today, if you mention CQRS, people assume you mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Two databases&lt;/strong&gt; - One for writes, one for reads. Greg never said this. You can do CQRS with a single table if you want.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Event Sourcing&lt;/strong&gt; - Yes, they work well together, but CQRS doesn’t require events at all. You can update the state directly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Eventual consistency&lt;/strong&gt; - Again, not required. Your read model can be updated synchronously in the same transaction.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Message queues&lt;/strong&gt; - Kafka, RabbitMQ, whatever. Nope. You can do CQRS in a single process with direct method calls.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Complex architecture&lt;/strong&gt; - People think CQRS means microservices, distributed systems, and architectural astronautics. It doesn’t.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The original idea - separate commands and queries - is simple and useful. It’s arguably simpler than mixing reads and writes in the same objects. But semantic diffusion has turned it into a boogeyman.&lt;/p&gt;
&lt;p&gt;This simplicity enables multiple options and extensions. And that posed the risk we’re facing—the risk of misinterpretation.&lt;/p&gt;
&lt;h3 id=&quot;vertical-slices-turn-for-diffusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#vertical-slices-turn-for-diffusion&quot; aria-label=&quot;vertical slices turn for diffusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Vertical Slices turn for diffusion&lt;/h3&gt;
&lt;p&gt;I think that it’s actually good that Jimmy coined a new term instead of reusing CQRS. He (sub?)consciously tried to avoid Semantic Diffusion. But that didn’t help in getting this issue for VSA.&lt;/p&gt;
&lt;p&gt;Now I’m watching the same thing happen to Vertical Slices Architecture. Jimmy Bogard’s original description was clear: organise code by features, not by technical layers. Couple along the axis of change.&lt;/p&gt;
&lt;p&gt;But already the corruption has begun:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;“You can’t share any code between slices”&lt;/strong&gt; - Jimmy never said this. He said to minimise coupling between slices. Minimise isn’t zero.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;“Every slice must have its own database table”&lt;/strong&gt; - Where did this come from? Slices can share storage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;“You must copy-paste everything”&lt;/strong&gt; - No. You can extract common patterns when they prove themselves useful.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;“You need &lt;a href=&quot;https://github.com/LuckyPennySoftware/MediatR&quot;&gt;MediatR&lt;/a&gt;”&lt;/strong&gt; (in .NET) - VSA is an architectural pattern, not a library choice.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;“Slices must have exactly this folder structure”&lt;/strong&gt; - Missing the point entirely. The structure should emerge from your needs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;“A vertical slice is an entire module”&lt;/strong&gt; - Some people argue that a vertical slice should be an entire business module (like “Billing” or “Inventory”). But Jimmy’s original article talks about individual features - specific user requests. A single command or query. The confusion about granularity shows how far we’ve drifted from the source.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;People teaching VSA often haven’t read Jimmy Bogard’s original article. Some don’t even know who coined the term.&lt;/p&gt;
&lt;p&gt;Even worse, some communities are developing their own interpretations where vertical slices mean something completely different (e.g. modelling processes).&lt;/p&gt;
&lt;p&gt;Each person who retells the pattern adds new constraints, removes important nuances, or changes the meaning to fit their agenda.&lt;/p&gt;
&lt;h3 id=&quot;why-semantic-diffusion-happens&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-semantic-diffusion-happens&quot; aria-label=&quot;why semantic diffusion happens permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why semantic diffusion happens&lt;/h3&gt;
&lt;p&gt;I think semantic diffusion happens because successful patterns attract attention. People want to be associated with them - &lt;em&gt;“we’re doing CQRS”&lt;/em&gt; sounds more impressive than &lt;em&gt;“we separated our reads and writes.”&lt;/em&gt; But most people learn about these patterns second or third-hand, and each retelling adds distortions.&lt;/p&gt;
&lt;p&gt;The pattern also shifts from principles to prescriptions. Where Greg Young said “separate commands and queries,” people hear “you must have two databases.” Where Jimmy Bogard said “minimize coupling between slices,” people hear “zero shared code allowed.” Nuance gets lost in favour of absolute rules that are easier to remember and teach.&lt;/p&gt;
&lt;p&gt;Framework authors and tool vendors contribute to this. They build “CQRS frameworks” or “VSA templates” that embed their own opinions and constraints. Soon, people can’t distinguish between the pattern and the tool’s implementation of it. The original simple idea gets buried under layers of accidental complexity.&lt;/p&gt;
&lt;p&gt;Teams reject CQRS because they think it requires eventual consistency. Others implement complex event sourcing and messaging systems when all they needed was to put queries in different files from commands. Instead of asking “what problem are we solving?” teams ask “are we doing real CQRS?” as if there’s a certification board checking their implementation.&lt;/p&gt;
&lt;h2 id=&quot;closing-thoughts&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#closing-thoughts&quot; aria-label=&quot;closing thoughts permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Both CQRS and Vertical Slices Architecture are great patterns.&lt;/strong&gt; They’re even more useful together - CQRS provides the conceptual model (separate commands and queries) while VSA provides the organisational principle (group by feature).&lt;/p&gt;
&lt;p&gt;But their usefulness depends on understanding what they actually are, not what common application has made them into.&lt;/p&gt;
&lt;p&gt;CQRS is &lt;em&gt;&lt;strong&gt;just&lt;/strong&gt;&lt;/em&gt; about separating commands and queries. VSA is &lt;em&gt;&lt;strong&gt;just&lt;/strong&gt;&lt;/em&gt; about organising by feature instead of the technical layer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’ve noticed that it’s easy to contribute to semantic diffusion without realising it.&lt;/strong&gt; You solve a hard problem with vertical slices, and suddenly, you’re seeing it as the answer for every system. Before you know it, you’re adding your own constraints and requirements that weren’t in the original.&lt;/p&gt;
&lt;p&gt;So next time someone tells you that CQRS requires event sourcing, or that VSA forbids shared code, ask where they read that. Most of the time, they didn’t read it anywhere - they just heard it from someone who heard it from someone else.&lt;/p&gt;
&lt;p&gt;So my final request is, if you want to learn about the pattern, try to get to the original sources. They’re usually shorter and clearer than you think.&lt;/p&gt;
&lt;p&gt;For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://cqrs.wordpress.com/wp-content/uploads/2010/11/cqrs_documents.pdf&quot;&gt;Greg Young - CQRS Documents&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.jimmybogard.com/vertical-slice-architecture/&quot;&gt;Jimmy Bogard - Vertical Slice Architecture&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/oAoaMlS1PWo?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Or check my take on CQRS:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/iY7LO289qnQ?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. This article will also be published on &lt;a href=&quot;/en/&quot;&gt;my blog&lt;/a&gt;. In the following weeks, I’m planning to centralise both this newsletter and my blog together. Stay tuned!&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Compilers Aren't Just for Programming Languages]]></title><description><![CDATA[Welcome to the new week! What does this streaming pipeline have to do with a programming language compiler? At first glance? Nothing! This…]]></description><link>https://event-driven.io/en/compilation-isnt-just-for-programming/</link><guid isPermaLink="false">https://event-driven.io/en/compilation-isnt-just-for-programming/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;What does this streaming pipeline have to do with a programming language compiler?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; deliveryPipeline &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  Async&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; order &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; restaurant &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetchRestaurantDetails&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;restaurantId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; restaurant&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timestamp&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;restaurant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isOpen&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  Async&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; order &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;driver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; route&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;findNearestDriver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;restaurant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;calculateRoute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deliveryAddress&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;restaurant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; driver&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; route &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    estimatedTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;route&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;duration &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;restaurant&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;prepTime
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;At first glance? Nothing!&lt;/p&gt;
&lt;p&gt;This looks like a typical functional pipeline that processes food delivery orders. But here’s what actually happens when you run this code: these operations don’t execute immediately. Instead, they create a description of what should happen - a data structure that represents your intent. Then, a compiler transforms this description into optimised code tailored to your specific JavaScript environment.&lt;/p&gt;
&lt;p&gt;This separation between declaring what you want and how it executes is the same fundamental principle that powers programming language compilers. When you write &lt;code class=&quot;language-text&quot;&gt;int x = 5 + 3&lt;/code&gt; in C, the compiler doesn’t just translate it word-for-word to assembly. It might optimise it to &lt;code class=&quot;language-text&quot;&gt;int x = 8&lt;/code&gt;, or eliminate it entirely if &lt;code class=&quot;language-text&quot;&gt;x&lt;/code&gt; is never used. The compiler understands your intent and finds the best way to achieve it.&lt;/p&gt;
&lt;p&gt;This same principle can apply to streaming pipelines. The pipeline definition above would just be data - a series of step descriptions. The actual execution would depend on where and how you run it.&lt;/p&gt;
&lt;h2 id=&quot;the-javascript-beautiful-mess&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-javascript-beautiful-mess&quot; aria-label=&quot;the javascript beautiful mess permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The JavaScript &lt;em&gt;Beautiful&lt;/em&gt; Mess&lt;/h2&gt;
&lt;p&gt;JavaScript’s ecosystem perfectly illustrates why we need this approach. &lt;a href=&quot;https://nodejs.org/api/stream.html&quot;&gt;Node.js has had streams&lt;/a&gt; since the beginning - they’re fast, battle-tested, and deeply integrated with the runtime. They power everything from file I/O to HTTP. But Node.js streams don’t exist in browsers. Instead, browsers implement the &lt;a href=&quot;https://streams.spec.whatwg.org/&quot;&gt;Web Streams standard&lt;/a&gt; - a completely different API with different behaviour, different backpressure mechanisms, and different performance characteristics.&lt;/p&gt;
&lt;p&gt;You might think:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Great, let’s use Web Streams everywhere since Node.js added support for them.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But here’s the reality: &lt;a href=&quot;https://nodejs.org/api/stream.html&quot;&gt;Web Streams in Node.js&lt;/a&gt; are a compatibility layer, not a native implementation. They’re slower than Node.js streams, sometimes significantly so. The &lt;a href=&quot;https://github.com/MattiasBuelens/web-streams-polyfill&quot;&gt;polyfill for environments&lt;/a&gt; that don’t support Web Streams? It’s not fully compliant and has its own performance issues.&lt;/p&gt;
&lt;p&gt;A naive implementation would force an impossible choice. Use Node.js streams, and your code won’t run in browsers. Use Web Streams, and you sacrifice performance in Node.js. Use async generators as a lowest common denominator, and you lose the benefits of native streaming entirely. Try to support everything with runtime checks, and you end up with:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;processStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; window &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;undefined&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; source &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadableStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Web Streams path&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; reader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ... different processing logic&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;typeof&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pipe &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;function&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Node.js streams path  &lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ... completely different API&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Symbol&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;asyncIterator &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Async generator fallback&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ... yet another approach&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ... and this is just the beginning&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This code quickly becomes unmaintainable. Each code path needs its own error handling, backpressure management, and performance optimisations. Worse, you can’t easily apply optimisations like operation fusion because the implementations are so different.&lt;/p&gt;
&lt;p&gt;As I explored in &lt;a href=&quot;/en/typescript-migrates-to-go-whats-really/&quot;&gt;my article about TypeScript’s performance&lt;/a&gt;, the key to JavaScript performance is keeping the event loop spinning and leveraging native APIs. But when those native APIs are incompatible across environments, we need a different approach.&lt;/p&gt;
&lt;p&gt;This is where compilation comes in. Instead of writing environment-specific code, we write a description of what we want to happen. A compiler then transforms this description into the optimal implementation for each environment. Same API, different execution. Write once, optimise everywhere.&lt;/p&gt;
&lt;p&gt;I’m building this approach into &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt;’s event processing system. &lt;strong&gt;Let’s call it codename&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;FusionStreams&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As I outlined in &lt;a href=&quot;/en/workflow-engine-design-proposal-tell/&quot;&gt;my workflow engine design&lt;/a&gt;, modern event-driven systems need to handle complex workflows - not just simple event handlers, but multi-step processes with error handling, compensation, and state management. These workflows need to run efficiently whether they’re processing events on a server, updating projections in a browser, or handling commands at the edge.&lt;/p&gt;
&lt;h2 id=&quot;how-compilers-actually-work&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-compilers-actually-work&quot; aria-label=&quot;how compilers actually work permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How Compilers Actually Work&lt;/h2&gt;
&lt;p&gt;Understanding how FusionStreams could work requires building it from the ground up. First, operations need representation as data:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Step&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;symbol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  fn&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  isSync&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When you write &lt;code class=&quot;language-text&quot;&gt;map(x =&gt; x * 2)&lt;/code&gt;, it doesn’t execute anything. It returns a step description. This is crucial - by delaying execution, we can examine the entire pipeline before running it. We can see patterns and opportunities for optimisation that would be invisible if we executed operations immediately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The compilation process transforms these step descriptions into executable functions.&lt;/strong&gt; Traditional compilers work in phases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;lexical analysis,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;parsing,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;semantic analysis,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;optimisation,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;and code generation.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our streaming compiler follows similar principles but is simpler.&lt;/p&gt;
&lt;p&gt;During the analysis phase, the compiler examines the pipeline structure. It identifies consecutive synchronous operations, async boundaries, and error handling points. This is where the first major optimisation happens - sync operation fusion.&lt;/p&gt;
&lt;p&gt;Consider this sequence:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;map(x =&gt; x + 1)
filter(x =&gt; x &gt; 10)  
map(x =&gt; x * 2)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A naive implementation would create three function calls for each item, allocating intermediate objects and jumping between functions. The compiler recognises these are all synchronous and fuses them into a single operation. Instead of three function calls, you get one tight loop. No intermediate allocations. The event loop keeps spinning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This optimisation is only possible because we separated declaration from execution.&lt;/strong&gt; If each operation were executed immediately, we couldn’t see the pattern. This is exactly how modern language compilers work - they analyse entire programs to find optimisation opportunities that aren’t visible when looking at individual statements.&lt;/p&gt;
&lt;p&gt;Early programmers wrote assembly directly. They counted cycles, allocated registers by hand, and knew exactly what the CPU would execute. As programs grew from hundreds to millions of lines, this approach collapsed under its own weight.&lt;/p&gt;
&lt;p&gt;High-level languages offered an escape, but early compilers were disappointingly literal. They translated your C code line-by-line into assembly, producing results that ran slower than hand-optimised code. Programmers faced an uncomfortable choice: write maintainable code that ran slowly, or write fast code that was impossible to maintain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Modern compilers changed this equation. They don’t translate - they analyse and transform.&lt;/strong&gt; When you write a for-loop that adds numbers, the compiler might recognise the pattern and replace it with a multiplication. It might notice your loop accesses memory sequentially and generate vector instructions. Or it might determine the result at compile time and replace the entire loop with a constant.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 630px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d147c421d1e49fafec981cc56fe711bb/48f8e/2025-08-18-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 116.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAAAsTAAALEwEAmpwYAAACIUlEQVR42pVV7XKjMAzM+79db9L71bm5EmJMIICB8BHAqlYECin4ckxJsGJvtdKuOFhLxH/rGx98NU1LaZpRGMYUBBeK44QyXjdNI7/PZxcYBxdgXTeU5yVlmaHTSVGaZGRMTlXlBLTzghag4wFLbXuXjMIwpKIoZT2dmTYuMQ7T4b2rZjClNJ08RedzwNnVtMD68bzK8JvyuGUYBgG43SpSgRbqWPccX1FdZfhUQ3pqijEFA+Wk9UUA87yQ2u7XcIPmFMNBAQUgd3lsSE1Vvab9RHk/w74fqCxvcisVSIZ47rr+P2WzqCMofv71yfN8bopmXZpNmblruHgGZWQVRTHlRTGK2roAHTVs2lbqhiy1DsU1qTil3ZTOC05pxXZoiGK6SoXimJedslXHijsNyhA15FNzdsNgHTr8h1MgE99X0hTPO4vIl0pwOoU2mtP3vdxRHIsu4Z4fgC6nbHXvfu8oSVIZDOTY95JsBIy1B6oJj68Z1DoAV5LhwPAI2oew4+gqdMNLRIbX877Bzhk4MxwFjeGayzemC/SI7Gr2MeKoK9GebCQw/kdI5Hh8p8wYOYTfMBPf3n6JU3B1HH8//qaPjz/7AxbdK8uKrteUxXuWsQXJNEwV7xKfBX3lb5mPvM/3A9HmLKPnGiI7GB9zT+tIHIGRD4p4xtTGcMA7BnXFSwt786mma8r20dG7+BQeRu26rpP6fcdaGWlzTNb9CvALE9kNWXd6gsYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d147c421d1e49fafec981cc56fe711bb/507e6/2025-08-18-cover.webp 200w,
/static/d147c421d1e49fafec981cc56fe711bb/28a80/2025-08-18-cover.webp 400w,
/static/d147c421d1e49fafec981cc56fe711bb/d869c/2025-08-18-cover.webp 630w&quot;
              sizes=&quot;(max-width: 630px) 100vw, 630px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d147c421d1e49fafec981cc56fe711bb/36ca5/2025-08-18-cover.png 200w,
/static/d147c421d1e49fafec981cc56fe711bb/a3397/2025-08-18-cover.png 400w,
/static/d147c421d1e49fafec981cc56fe711bb/48f8e/2025-08-18-cover.png 630w&quot;
            sizes=&quot;(max-width: 630px) 100vw, 630px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d147c421d1e49fafec981cc56fe711bb/48f8e/2025-08-18-cover.png&quot;
            alt=&quot;2025 08 18 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Each phase transforms the pipeline further from its declarative form into executable code. The analysis phase groups operations and identifies optimisation opportunities. The optimisation phase applies transformations like operation fusion. The code generation phase produces the final executable form tailored to the runtime environment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This evolution from direct execution to analysed transformation appears everywhere in software:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Java took this idea further with Just-In-Time compilation.&lt;/strong&gt; Your Java code compiles to bytecode, which the JVM then compiles again to machine code while your program runs, optimising based on actual usage patterns.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Database query planners are compilers too.&lt;/strong&gt; They transform declarative SQL into execution plans, choosing between nested loops, hash joins, or merge joins based on table statistics and available indexes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;.NET’s LINQ doesn’t execute queries directly.&lt;/strong&gt; When you write &lt;code class=&quot;language-text&quot;&gt;customers.Where(c =&gt; c.City == &quot;London&quot;).Select(c =&gt; c.Name)&lt;/code&gt;, it builds an expression tree. The LINQ provider then compiles this tree - to SQL for databases, to objects for in-memory collections, or to web service calls for remote data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;React’s new compiler analyses your components to understand data flow and dependencies.&lt;/strong&gt; It then generates optimised code that eliminates unnecessary re-renders by compile-time analysis of which props actually affect rendering.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each system recognised that separating “what you want” from “how to do it” enables optimisations impossible with direct execution. FusionStreams applies this same principle to streaming pipelines.&lt;/p&gt;
&lt;h2 id=&quot;from-theory-to-implementation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#from-theory-to-implementation&quot; aria-label=&quot;from theory to implementation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;From Theory to Implementation&lt;/h2&gt;
&lt;p&gt;Let’s see how this works with a more complex operation. When processing multiple async operations, you might naturally reach for parallel execution:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Async.map(async order =&gt; {
  const [driver, route] = await Promise.all([
    findNearestDriver(order.restaurant.location),
    calculateRoute(order.deliveryAddress, order.restaurant.location)
  ]);
  return { ...order, driver, route };
})&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This works, but what happens when you need to process thousands of orders? Without concurrency control, you might overwhelm your services. You may replace it later with a new operator mapParallel for processing multiple items in parallel, passing multiple functions returning promises. The compiled version could then manage a pool of concurrent operations, reuse resources, and adapt its strategy based on the environment.&lt;/p&gt;
&lt;p&gt;In Node.js, it might use worker threads for CPU-intensive operations. In a browser, it might use Web Workers or carefully time-slice execution to avoid blocking the UI. The declaration would stay the same, but the execution adapts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One powerful aspect of this approach would be extensibility.&lt;/strong&gt; Because operations are just data structures, users could define their own operations and register compilers for them.&lt;/p&gt;
&lt;p&gt;For instance, real-world streaming pipelines rarely follow a simple linear flow. They branch, merge, and coordinate complex operations. The fanout pattern shows how compilation enables these sophisticated patterns while maintaining performance. When processing a food delivery order, you need to notify the kitchen, find a driver, and process payment simultaneously:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;fanout({
  kitchen: pipe(
    Async.map(order =&gt; notifyKitchen(order)),
    Sync.map(response =&gt; ({
      orderId: order.id,
      prepTime: response.estimatedMinutes
    }))
  ),

  driver: pipe(
    Async.map(order =&gt; findDriver(order.restaurant.location))
  ),

  payment: pipe(
    Async.map(order =&gt; chargePayment(order.payment))
  )
})&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;During compilation, this pattern transforms based on the environment’s capabilities. If worker threads are available, each branch might run in true parallel. Without them, the compiler might use Promise.all for concurrent execution. In memory-constrained environments, it might process branches sequentially. The same declaration compiles to different strategies.&lt;/p&gt;
&lt;p&gt;Want rate limiting? Define what it means declaratively, then provide compilers that implement it efficiently for each environment.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;RATE_LIMIT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Symbol&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;rateLimit&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rateLimit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestsPerSecond&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Step &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;RATE_LIMIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; requestsPerSecond &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    isSync&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The rate-limiting compiler could use token buckets in memory for single-instance Node.js apps, Redis for distributed systems, or localStorage in browsers. The pipeline definition wouldn’t change - only the compilation target.&lt;/p&gt;
&lt;p&gt;Naive implementation could work as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;builder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;registerSyncCompiler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;RATE_LIMIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;step&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; requestsPerSecond &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; step&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; requestsPerSecond&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; lastRefill &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; elapsed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; lastRefill&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;requestsPerSecond&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; elapsed &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; requestsPerSecond&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    lastRefill &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      tokens &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        ok&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Rate limit exceeded: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;requestsPerSecond&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; requests/second&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;context
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;This extensibility would transform the system from a fixed library into a compilation framework.&lt;/strong&gt; Domain-specific operations could compile to efficient implementations. A database operation could compile to use connection pooling in Node.js but IndexedDB transactions in browsers. A caching operation could use Redis in production but memory in development.&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Building event-driven systems that work across various JavaScript environments presents a specific technical challenge.&lt;/strong&gt; Each environment has different APIs and performance constraints. The traditional solution - writing environment-specific code or using compatibility layers - leads to either maintenance headaches or performance compromises.&lt;/p&gt;
&lt;p&gt;Compilation offers a different trade-off. By treating pipeline definitions as data to be transformed rather than code to be executed, you can generate environment-specific implementations from a single source. The complexity moves from runtime to build time, where it can be managed systematically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This approach works because it acknowledges that abstraction and performance don’t have to conflict - they conflict when the abstraction exists at runtime.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A compiler can understand that three consecutive map operations should become one function, that Node.js should use native streams while browsers use web streams, or we can fallback to async generators. These optimisations are mechanical transformations, not clever hacks.&lt;/p&gt;
&lt;p&gt;The same pattern appears whenever systems process declarative definitions. SQL becomes query plans. GraphQL becomes resolver calls. Configuration becomes lookup tables. In each case, the transformation from declaration to execution provides an opportunity for optimisation that interpretation can’t match.&lt;/p&gt;
&lt;p&gt;For event-driven architectures, where the same business logic might run in vastly different contexts with vastly different performance requirements, this separation becomes essential. The alternative is maintaining multiple implementations or accepting suboptimal performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compilation provides a third path: write the logic once, optimise for each target when you need it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Requiem for a 10x Engineer Dream]]></title><description><![CDATA[Welcome to the new week! People claim they get 10x productivity boosts with AI coding tools. After my recent experiments with Claude Code, I…]]></description><link>https://event-driven.io/en/requiem-for-a-10x-engineer-dream/</link><guid isPermaLink="false">https://event-driven.io/en/requiem-for-a-10x-engineer-dream/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;People claim they get 10x productivity boosts with AI coding tools. After my recent experiments with Claude Code, I’m starting to think we’re not using these tools the same way. Or that they’re just lying. Or both.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Those hyped posts seem to me like teenagers talking about sex. A lot of talking, not much doing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yes, I’m LLM-sceptic.&lt;/strong&gt; But honestly, when I did a few times quick check and asked:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Ok, so what’s your development flow”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then most of the answers I got could be summarised into:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Well, I’m typing and copy/pasting into chat”.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“I’m doing Tab, Tab in Copilot.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Fine, that’s also what I was doing. Even as a sceptic (or hater, if you prefer), I like to try stuff on my own, before I make a judgement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I see the biggest potential improvement in LLMs in the boring, repetitive tasks, ideally done in the background without much intervention from me.&lt;/strong&gt; Maybe then I could get some boost working as a solopreneur. In theory, that’s what the new wave of CLI tooling promises.&lt;/p&gt;
&lt;p&gt;I decided to give it a try and even paid for Claude Code Max.&lt;/p&gt;
&lt;p&gt;I tried to find a task that would be a stereotypical perfect match for an AI assistant.&lt;/p&gt;
&lt;p&gt;I have a workshop for &lt;a href=&quot;/en/training/#event-driven-architecture-the-light-and-the-dark-side&quot;&gt;The Light and the Dark Side of Event-Driven Architecture&lt;/a&gt;. I wanted to expand some hands-on exercises to use RabbitMQ and Kafka instead of an in-memory implementation. Sure, “adding Kafka” sounds heavy, but it’s not that.&lt;/p&gt;
&lt;p&gt;I started with Java exercises. They use Spring Boot, which streamlines Kafka and RabbitMQ setup. Plus there are many resources in the Internet on how to do the setup. Essentially, the goal was to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;copy/paste existing code, that had tests, working implementation,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;replace the in-memory implementation with Kafka or RabbitMQ,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add Spring Boot config for Kafka or RabbitMQ,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update the test setup to use TestContainers and add some awaits respecting the async nature of those tools.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So do the boilerplate that I didn’t want to spent much time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I wrote a detailed spec and hoped Claude could handle this boring work while I did something else.&lt;/strong&gt; After a week, even with hints from me, it was still spinning in circles, unable to finish.&lt;/p&gt;
&lt;p&gt;Yet it was claiming that it got a huge success, getting 80% of tests passing. It wasn’t even able to complete the proper setup. Indeed, great success for the code that had a 100% passing rate. So much for autonomous AI assistance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tho I’m not the person who easily gives up. Last Friday, I tried again, this time staying fully engaged. I wanted to refactor &lt;a href=&quot;https://github.com/event-driven-io/pongo&quot;&gt;Pongo&lt;/a&gt;’s SQL handling.&lt;/strong&gt; I wanted to make it generate parameterised queries instead of the raw ones. Thanks to that, getting better performance by benefiting from the query plan reuse.&lt;/p&gt;
&lt;p&gt;Again, that can sound complex, but it’s more complicated than complex. Mostly, it had to refactor how SQL was parsed, and update the execution to use not only raw SQL but also parameters.&lt;/p&gt;
&lt;p&gt;With the Claude Code setup, I followed the &lt;a href=&quot;https://harper.blog/2025/05/08/basic-claude-code/&quot;&gt;nice guide from Harper Reed&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;brainstorming,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;spec generation,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;implementation plan with prompts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everyone is saying that you need to give LLM a precise plan to make it work correctly. And that’s entirely true. I managed to get acceptable results, especially after doing the final polish on my own. So what’s the story then?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I also realised the fundamental flaw in this whole enterprise.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The more detailed you make your prompts, the better Claude Code performs. But to get it to work correctly, you need specifications so detailed that you’ve almost done the implementation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You’re not necessarily saving time - you’re programming in Markdown instead of code.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It’s even worse than that, though. To be good at it, you need to become the world’s most annoying micromanager.&lt;/p&gt;
&lt;p&gt;You know the type - watching over someone’s shoulder, correcting every keystroke, eventually grabbing the keyboard.&lt;/p&gt;
&lt;p&gt;Just like my childhood friend Gabriel. He was the first one from the block to get a personal computer. Yes, I’m that old. You had to wait until he let you play Mortal Combat on his Amiga 600. But then you need to cherish your moment, as he was watching from behind, and quickly got annoyed and shouted:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“that’s not how you play!”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then, grabbing the joystick to “show you how it should be done.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, to be effective, you need to behave just like Gabriel. You need to watch Claude plotting stuff in the console.&lt;/strong&gt; As one moment it generates sensible code, then completely loses the plot the next.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When you hit unexpected problems (and you always do), Claude can’t assess whether it needs help.&lt;/strong&gt; I told it to ask for clarification in these situations. Sometimes it does, usually it doesn’t, preferring to solve everything by throwing more code at it. Mountains of code. Complex solutions to simple problems.&lt;/p&gt;
&lt;p&gt;Also, if you’d like to make it autonomous, working in the background, then you need to provide a longer plan. The longer it gets, the longer it needs to be worked on. Adding to that, it has to be precise; the likelihood and the consequences of missing something important will get more severe. Of course, we can fix the plan and regenerate it, but…&lt;/p&gt;
&lt;p&gt;We’ve seen this movie before. It’s waterfall development with extra steps. &lt;a href=&quot;https://en.wikipedia.org/wiki/Computer-aided_software_engineering&quot;&gt;Remember UML tools&lt;/a&gt;? I still have nightmares from IBM Rational RequisitePro. They promised that we were going to model the world perfectly, then generate all the code. Just describe every class, every relationship, every edge case in beautiful diagrams. The tools would handle the mundane coding part.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It failed because the specification was never the hard part - it was discovering what to build while building it.&lt;/strong&gt; Now we’re making the same bet with prompts, hoping that if we just describe things clearly enough, the implementation will handle itself.&lt;/p&gt;
&lt;p&gt;After a day of this, Claude had produced something that technically worked. But was it faster than doing it myself? Doubtful. Plus, I had to fix everything afterwards.&lt;/p&gt;
&lt;p&gt;Someone might say, “but you’re experienced, it helps juniors more.” Except that less experienced developers have even less ability to judge if Claude’s solutions are correct or completely insane.&lt;/p&gt;
&lt;p&gt;Speaking about junior devs. Many people claim that working with LLM is like working with a junior. I think that’s disrespectful and just plain wrong. Junior devs don’t have enough knowledge yet, but they learn, you can teach them, mentor them, and they will get better. They can also reason and react based on what they’re doing; they’re not just code outputters. LLMs won’t learn, as they don’t have memory; they just have context, which they happen to lose quickly and randomly.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;And another aspect: joy. We’re hearing a lot about vibes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But what vibes are they? Are they more like:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/apBWI6xrbLY?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Or actually more like?&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/N0L_wal-0cI?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;A lot of us came to programming to express our creativity. The puzzle-solving, the flow state, and the satisfaction of building something with our own hands.&lt;/p&gt;
&lt;p&gt;Replace that with prompt engineering and micromanagement, and you’ve sucked all the fun out of the room.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I spent a day watching an LLM struggle to generate sensible code. It was exhausting.&lt;/strong&gt; Not the good exhaustion of solving hard problems, but the bad kind - like teaching someone who forgets everything between lessons. If this is the future of programming, we’re going to see a new kind of burnout: LLM burnout. Death by a thousand prompts.&lt;/p&gt;
&lt;p&gt;Those tools have a sneaky UI with intentional &lt;a href=&quot;https://en.wikipedia.org/wiki/Gamification&quot;&gt;gamification&lt;/a&gt;. It’s kinda addicting watching those CLI and trying to force it to do what you asked. It’s like playing an MMORPG game. Still, it’s exhausting.&lt;/p&gt;
&lt;p&gt;We already established &lt;a href=&quot;https://en.wikipedia.org/wiki/Zoom_fatigue&quot;&gt;Zoom fatigue&lt;/a&gt;, and I believe that we’re also about to establish LLM fatigue.&lt;/p&gt;
&lt;p&gt;Of course, those tools will get better, any LLM fan will tell you that, as talking with GenAI fans is like talking to TV Series fans:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Dude, check those series, they’re great! I mean, the first five seasons might not seem impressive if you’re not into such series, but watch it, the 6th season will be mindblowing!”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I could also improve my prompting skills, but will it change the scale of the issue? I’ll try, but for now, I doubt that it’ll change the outcome substantially.&lt;/p&gt;
&lt;p&gt;There are practical uses of those tools. There’s value in using Claude for brainstorming. As a solopreneur, I don’t always have someone to challenge my ideas. Having something respond, even imperfectly, beats talking to yourself. But let’s be honest about what’s happening - we’ve invented a very expensive rubber duck that occasionally contributes ideas and can be useful to solving specific, narrowed-down coding challenges.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maybe I’m missing something. Maybe those 10x gains are real for others.&lt;/strong&gt; But after two serious attempts with real-world tasks, I’m convinced we’re chasing a mirage. We’re not getting dramatic productivity improvements. We’re getting a new flavour of an old fantasy - that if we just specify things precisely enough, computers will do the hard work for us.&lt;/p&gt;
&lt;p&gt;So do you really want to be like Gabriel?&lt;/p&gt;
&lt;p&gt;Do we want to believe again that micromanagement is good management? Or that waterfall this time will work?&lt;/p&gt;
&lt;p&gt;Or maybe you want to be a Markdown Programmer?&lt;/p&gt;
&lt;p&gt;What are your thoughts? Did you find the workflow or type of tasks that work for you?&lt;/p&gt;
&lt;p&gt;Check also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/&quot;&gt;Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://colton.dev/&quot;&gt;No, AI is not Making Engineers 10x as Productive by Colton Voege&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why Open Source Isn't Always Fair. Dual licenses explained]]></title><description><![CDATA[Welcome to the new week! Rug pull! Open Source drama! Yet another License change! We’re being hit recently by headers on social media. And…]]></description><link>https://event-driven.io/en/why-open-source-isnt-always-fair/</link><guid isPermaLink="false">https://event-driven.io/en/why-open-source-isnt-always-fair/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Rug pull!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Open Source drama!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Yet another License change!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We’re being hit recently by headers on social media. And guess what, I’m also making a similar change in my OSS projects. And let me explain to you why.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using my projects as an example, I’ll try to explain to you the current OSS landscape and licensing details. And why you should care!&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;whats-the-story-morning-glory&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#whats-the-story-morning-glory&quot; aria-label=&quot;whats the story morning glory permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What’s the story, Morning Glory?&lt;/h2&gt;
&lt;p&gt;I propose dual licensing Emmett and Pongo under &lt;a href=&quot;https://www.gnu.org/licenses/agpl-3.0.html&quot;&gt;AGPLv3&lt;/a&gt; and &lt;a href=&quot;https://www.mongodb.com/legal/licensing/server-side-public-license&quot;&gt;SSPL&lt;/a&gt;. Users choose which license suits their needs. The intention is to establish clear, flexible licensing for users and due diligence to prevent exploitation, enabling a sustainable model for maintainers.&lt;/p&gt;
&lt;h2 id=&quot;problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#problem&quot; aria-label=&quot;problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Problem&lt;/h2&gt;
&lt;p&gt;Emmett has no license by design. Without an explicit license, anyone wanting to use it should ask permission or clarification. This was intentional, but not for gatekeeping. I didn’t actually enforce that on anyone.&lt;/p&gt;
&lt;p&gt;I kept it this way to avoid the trap of starting with a permissive license and later changing terms, which communities see as &lt;em&gt;&lt;a href=&quot;https://redmonk.com/jgovernor/2024/09/13/open-source-foundations-considered-helpful/&quot;&gt;“rug pull”&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Pongo uses Apache 2.0, allowing anyone, including cloud providers, to take the code, wrap it in their services, and sell access without contributing back.&lt;/p&gt;
&lt;p&gt;From the beginning, I’ve been transparent: to work on Emmett and Pongo properly, I need to make them sustainable. This isn’t theoretical. I maintained Marten for years, and despite its success, I couldn’t make it financially sustainable. Licensing won’t make it sustainable, so I don’t plan to provide a paid license for the core code, but it’s due diligence to set the right legal foundations.&lt;/p&gt;
&lt;p&gt;Now, with contributors who’ve put significant work into these projects, I need to formalise a structure that’s fair to them while creating the sustainability I’ve always said I needed.&lt;/p&gt;
&lt;h2 id=&quot;background-the-pattern-of-exploitation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#background-the-pattern-of-exploitation&quot; aria-label=&quot;background the pattern of exploitation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Background: The Pattern of Exploitation&lt;/h2&gt;
&lt;p&gt;The current Open Source model assumes symmetry between all users. But as David Whitney argues, in his NDC talk “Open-Source Exploitation”, when there’s a power imbalance between the largest organisations in the world and someone putting code on the internet, the organisation always benefits. When the OSI insists that cloud providers deserve equal treatment to individual developers, it forces projects into defensive positions.&lt;/p&gt;
&lt;p&gt;The database world has experienced this firsthand:&lt;/p&gt;
&lt;h3 id=&quot;mongodbs-stand-2018&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#mongodbs-stand-2018&quot; aria-label=&quot;mongodbs stand 2018 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MongoDB’s Stand (2018)&lt;/h3&gt;
&lt;p&gt;MongoDB thrived under AGPL until cloud vendors began offering MongoDB as a commercial service, keeping all revenue without contributing back. &lt;a href=&quot;https://techcrunch.com/2018/10/16/mongodb-switches-up-its-open-source-license/&quot;&gt;MongoDB created SSPL with the intent&lt;/a&gt;: if you offer our database as a service, open source your entire service infrastructure or pay us.&lt;/p&gt;
&lt;p&gt;AWS built DocumentDB, a proprietary MongoDB-compatible database. MongoDB protected itself but lost AWS as a user. They remain SSPL-only, never adding back an OSI-approved option.&lt;/p&gt;
&lt;h3 id=&quot;elastics-confrontation-2021&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#elastics-confrontation-2021&quot; aria-label=&quot;elastics confrontation 2021 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Elastic’s Confrontation (2021)&lt;/h3&gt;
&lt;p&gt;Elastic’s case reveals deeper issues. AWS offered Elasticsearch as a managed service and using the project name. Besides the similar sustainability issue as MongoDB had, that also created the confusion. The responsibility for fixes were conflated, and too often Elastic was blaimed for the issues that were specific to AWS Managed service.&lt;/p&gt;
&lt;p&gt;The relationship deteriorated. Elastic switched from Apache 2.0 to dual licensing under SSPL and their proprietary Elastic License. AWS forked Elasticsearch to create OpenSearch, maintaining the Apache 2.0 license.&lt;/p&gt;
&lt;p&gt;But here’s the twist: &lt;a href=&quot;https://ir.elastic.co/news/news-details/2024/Elastic-Announces-Open-Source-License-for-Elasticsearch-and-Kibana-Source-Code/default.aspx&quot;&gt;in 2024, Elastic added AGPLv3 as a third license option&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;rediss-preemption-2024&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#rediss-preemption-2024&quot; aria-label=&quot;rediss preemption 2024 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Redis’s Preemption (2024)&lt;/h3&gt;
&lt;p&gt;Similar twist happened to Redis. In March 2024, they &lt;a href=&quot;https://redis.io/blog/redis-adopts-dual-source-available-licensing/&quot;&gt;switched from BSD to dual licensing under SSPL and RSALv2&lt;/a&gt;. Within weeks, the Linux Foundation announced Valkey, a Redis fork backed by AWS, Google, and others.&lt;/p&gt;
&lt;p&gt;In 2025, &lt;a href=&quot;https://www.theregister.com/2024/03/22/redis_changes_license/&quot;&gt;Redis added AGPLv3 with Redis 8, integrating features from their previously commercial Redis&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The pattern becomes clear.&lt;/p&gt;
&lt;h3 id=&quot;why-the-return-to-open-source&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-the-return-to-open-source&quot; aria-label=&quot;why the return to open source permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why the Return to Open Source?&lt;/h3&gt;
&lt;p&gt;Elastic and Redis (but not MongoDB) added AGPLv3 after their license changes. Two reasons:&lt;/p&gt;
&lt;p&gt;First, SSPL isn’t recognised as open source by the OSI because it discriminates against offering software as a service. The OSI maintains that open source must treat all users equally—even when those users are trillion-dollar companies exploiting smaller projects.&lt;/p&gt;
&lt;p&gt;Second, strategy. AGPLv3 allows projects to incorporate improvements from forks legally. You can’t take BSD-licensed code from Valkey and add it to SSPL Redis. But between compatible copyleft licenses? That works. Yes, also if there’s more than one license for the same code.&lt;/p&gt;
&lt;p&gt;The dual licensing trend represents adaptation to this reality. It’s not ideal, but it’s pragmatic.&lt;/p&gt;
&lt;h2 id=&quot;the-licenses-explained&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-licenses-explained&quot; aria-label=&quot;the licenses explained permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Licenses Explained&lt;/h2&gt;
&lt;h3 id=&quot;permissive-licenses-the-current-problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#permissive-licenses-the-current-problem&quot; aria-label=&quot;permissive licenses the current problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Permissive Licenses: The Current Problem&lt;/h3&gt;
&lt;p&gt;Apache 2.0, MIT, and BSD say: take this code, do whatever you want, just give attribution. This generosity becomes vulnerability when cloud providers monetise your work without reciprocating.&lt;/p&gt;
&lt;h3 id=&quot;agplv3-network-copyleft&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#agplv3-network-copyleft&quot; aria-label=&quot;agplv3 network copyleft permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;AGPLv3: Network Copyleft&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gnu.org/licenses/agpl-3.0.html&quot;&gt;AGPLv3&lt;/a&gt; extends GPL’s copyleft to network use. If you modify AGPLv3 software and users interact with it over a network, you must provide the source code of your modifications.&lt;/p&gt;
&lt;p&gt;“Users interacting over a network” means any service where users access the functionality—web applications, APIs, any network protocol. This applies only to modifications of the licensed software itself.&lt;/p&gt;
&lt;p&gt;Many developers misunderstand this. They believe AGPLv3 requires open sourcing their entire application. It doesn’t. If you build a banking system using Emmett, your business logic remains proprietary. Only if you modify Emmett’s core event sourcing engine and expose that modified version as a service would you need to share those Emmett modifications.&lt;/p&gt;
&lt;h3 id=&quot;sspl-explicit-service-protection&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#sspl-explicit-service-protection&quot; aria-label=&quot;sspl explicit service protection permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;SSPL: Explicit Service Protection&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mongodb.com/legal/licensing/server-side-public-license&quot;&gt;SSPL&lt;/a&gt; takes AGPLv3 and replaces Section 13. The new text:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;if you make the functionality of the software available to third parties as a service, you must release the “Service Source Code”—all management software, user interfaces, APIs, automation, monitoring, backup systems, storage software, hosting software.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If AWS (or any other company) wants to offer “Emmett-as-a-Service,” they’d need to open source their AWS infrastructure used to run it. For normal usage—building applications—SSPL behaves exactly like AGPLv3.&lt;/p&gt;
&lt;h3 id=&quot;the-fair-code-movement&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-fair-code-movement&quot; aria-label=&quot;the fair code movement permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Fair-Code Movement&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://faircode.io/&quot;&gt;Fair-code&lt;/a&gt; describes software that is free to use, has source code available, can be modified and distributed, but includes commercial restrictions. As &lt;a href=&quot;https://www.skool.com/content-academy/are-you-using-n8n-open-source-did-you-know-this&quot;&gt;n8n explains&lt;/a&gt;, cloud providers capture value created by open source projects with little return to the original developers.&lt;/p&gt;
&lt;p&gt;N8n created the &lt;a href=&quot;https://docs.n8n.io/sustainable-use-license/&quot;&gt;Sustainable Use License&lt;/a&gt;, acknowledging that open source doesn’t mean fair. Fair-code isn’t a software license but a model where software is free to use and distribute, but commercially restricted by its authors.&lt;/p&gt;
&lt;p&gt;We don’t want a scenario like that:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 385px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/81ead666e0e03d04dce9e8dc64d70284/b99b7/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 127%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAIAAAC+dZmEAAAACXBIWXMAAAsTAAALEwEAmpwYAAACsklEQVR42oXU6U5iQRAFYN//FXwAg4kS1LjvG44gihfcV1RwQXFM3BXmC404MjjWj07d7j5VdU5V346dnZ2Hh4eTk5NisXh9fb21tbW/v7+7u2vn6Ojo4ODg9PT07u7u919WqVSenp5WVlY6YGq1WqFQWF9f58/Pz09NTc3NzUVRZF1YWNjb23OhWq3WPuz9/d0qTQPsTKSlpaVsNru8vMx3Jlw6na7+Y29vbw1wqVTilcvlq6sr9aiqVDdBrRcXF7e3t7Wv9pk5gLEKhaH6/PzMUW13d7f9+/v7H8AvLy/W19fXWCwWUiHf2dl5eHjYQrgN57W1NQmlBQ4H2FJRCMl/EGxkZCSVSmnV4OCgT8qNjY2trq4ODQ0R4ltwKLu3t5fO7gnhc2NjIx6Pa9Xo6Ghg1B58fn7OSyQSci4uLo6Pjz8+PqLdLPtnzoZhcnISUjYkdVItNvP5vG59q/bl5aVZg5HKbHDs5nI5flS3np4eFwKmdUiAzTa1j4+PTbV5ptbAwIAmmbDt7W3l/C8zksrzAOCxmJmZ6erqwmJ6eprmBDd/ZmZzc1N0ArtsED2eBlh5mgwsYTKZlJ/jqtXDcirW7Owsx+RrAZRwrZkZ2hqWyWT02VWrJDaBbZLGqaDecoOzJofXy1SLbXjYStVzV4E1Uk4hgEW036ET1Pv1YW54z+F5hjzCkQ15vlP98ymluI0hUZiyC3WjGf/s7MyKmHvkoL+B6e/vlyBo/jlhOIuEhpWvJI5jCpGX2mox+XIK3TphkvT19aE0MTFBJOHVrEI7Bp4oetOc0zZg94aHh1H1Nv0ArOQgHv2NuqcOGYbsy5Dc3Nyk6yanOtVGAmwdh/rDz6T5PD7BAoMZjGZvrXwkOVYKa6QEbcrWTJopmzCq4CMSnLDDtAAYEePkkZAAOIqiP6JiME3V4CxKAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/81ead666e0e03d04dce9e8dc64d70284/507e6/image-2.webp 200w,
/static/81ead666e0e03d04dce9e8dc64d70284/d03c8/image-2.webp 385w&quot;
              sizes=&quot;(max-width: 385px) 100vw, 385px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/81ead666e0e03d04dce9e8dc64d70284/36ca5/image-2.png 200w,
/static/81ead666e0e03d04dce9e8dc64d70284/b99b7/image-2.png 385w&quot;
            sizes=&quot;(max-width: 385px) 100vw, 385px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/81ead666e0e03d04dce9e8dc64d70284/b99b7/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This is not fair to anyone and is highly dangerous to users. From both the service continuity and &lt;a href=&quot;https://en.wikipedia.org/wiki/XZ_Utils_backdoor&quot;&gt;security perspectives&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-proposal&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-proposal&quot; aria-label=&quot;the proposal permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Proposal&lt;/h2&gt;
&lt;p&gt;Dual license Emmett and Pongo under AGPLv3 and SSPL. Users choose:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AGPLv3 if&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Your organization requires OSI-approved licenses,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You understand and accept network copyleft,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want maximum compatibility with OSS tooling.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SSPL if&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You worry AGPLv3 might require open sourcing your application (it doesn’t, but SSPL makes this clearer),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You want explicit protection that using Emmett/Pongo doesn’t affect your application code,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You prefer the strongest terms.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is explicitly pro-user. Those preferring OSI-approved licenses choose AGPLv3. Those wanting clearer terms choose SSPL. Those licences ensure that the core code of Emmett and Pongo remains open.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d9871ba0c982fbc6663b57565dec1794/cdad8/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB6ElEQVR42j1S147bMBDUB1xzka3eSIpUsyy53Nm4APn/n5oMN5c8DBZclp3CIIxL/ENtBmRVi31UYHvIsAkThEkh67Q0Ul8+DnjbHNnPuM4R50ru+v77NkKguxnj8gk7XtAOC/rTDXU7Iso1oqqGnU9o2gnHrOHFCnvifZ+i6p2cyxvHh0vsOOwjzBDUdobuFzTuDNWxcu3h+7nuMV0faMcrhuULbrohKlqyUyhtj1wNAjnLuo9rBCUvmf4MQ6banX7YKTJS2Cc5CmNpg5VeUhhh87qJkBmFlPbEXgnP+rrxDM2wwlGmr55lRXkex9wgaQzcsnJN2W4WBVnTY0fZbl3QzXdYsu7mT6q44MCHAzuu6OcbcYf3s1AdwxkZAqdXDT08/9gwcSgHUpFnMlxWzNcnuukqd/07h4SSWzIbznf6c2U4X1BkUupBQgnTipMX6QlzDmrsiR7WyJSVfkwbfBjbY4EdESjKNP0qSOhVWjka3OOQasS1RkuGyi1ISium76JKoE4jFS0SiLcnTNXfULyUlqFYfpluuqCkZG+2TGQolevEdN/zAYZJhVf+t1wrFI0lQ02bnOxtjzkC/x0uj9+Yb9/EL36PB8b1IQGllKVH+qpHlGb6z2bHRzXX0/rk2aeE5feiwuAPzFMaxrlk8pwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d9871ba0c982fbc6663b57565dec1794/507e6/image-3.webp 200w,
/static/d9871ba0c982fbc6663b57565dec1794/28a80/image-3.webp 400w,
/static/d9871ba0c982fbc6663b57565dec1794/8d2ea/image-3.webp 800w,
/static/d9871ba0c982fbc6663b57565dec1794/cdaf6/image-3.webp 1013w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d9871ba0c982fbc6663b57565dec1794/36ca5/image-3.png 200w,
/static/d9871ba0c982fbc6663b57565dec1794/a3397/image-3.png 400w,
/static/d9871ba0c982fbc6663b57565dec1794/a331c/image-3.png 800w,
/static/d9871ba0c982fbc6663b57565dec1794/cdad8/image-3.png 1013w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d9871ba0c982fbc6663b57565dec1794/a331c/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;implementation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#implementation&quot; aria-label=&quot;implementation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Implementation&lt;/h2&gt;
&lt;h3 id=&quot;license-files&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#license-files&quot; aria-label=&quot;license files permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;License Files&lt;/h3&gt;
&lt;p&gt;There will be added License files explaining the licensing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;LICENSE-AGPL.txt (&lt;a href=&quot;https://www.gnu.org/licenses/agpl-3.0.txt&quot;&gt;GNU AGPLv3&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LICENSE-SSPL.txt (&lt;a href=&quot;https://www.mongodb.com/legal/licensing/server-side-public-license&quot;&gt;MongoDB SSPL&lt;/a&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;LICENSE.md explaining dual licensing and selection&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;contributor-license-agreement&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#contributor-license-agreement&quot; aria-label=&quot;contributor license agreement permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Contributor License Agreement&lt;/h3&gt;
&lt;p&gt;To contribute code to Emmett or Pongo, people will need to sign a Contributor License Agreement. This ensures legal clarity and protects both contributors and maintainers. The key terms of the CLA are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rights Granted&lt;/strong&gt; - contributors grant the project a license to use, modify, and distribute your contributions under both AGPLv3 and SSPL. This allows users to choose either license and ensures consistency across the codebase.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Copyright&lt;/strong&gt; - Contributors retain full copyright over their contributions. The project does not take ownership of your work. Attribution will be maintained as required by both licenses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Future License Options&lt;/strong&gt; - Contributors allow the project to add additional licenses in the future, provided your contribution remains available under AGPLv3 and SSPL. This flexibility enables adaptation to changing legal or strategic needs while preserving existing license paths.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Contributor Protection&lt;/strong&gt; - The CLA does not allow the project to relicense your work under more permissive or proprietary terms unless explicitly stated and agreed upon. The CLA applies only to the specific contributions you submit to this project.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Why the CLA Is Needed?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It ensures clear legal rights for all users of the code under both licenses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It protects contributors from unintended license violations or uncertainty.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It supports a long-term strategy of sustainability and legal defensibility.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A link to the full CLA text will be provided in the project repository. Signing the CLA is required before pull requests can be merged. There will be CLA bot added to the GH pull request process.&lt;/p&gt;
&lt;h2 id=&quot;sustainability-model&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#sustainability-model&quot; aria-label=&quot;sustainability model permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Sustainability Model&lt;/h2&gt;
&lt;h3 id=&quot;open-source-core&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#open-source-core&quot; aria-label=&quot;open source core permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Open Source Core&lt;/h3&gt;
&lt;p&gt;Emmett and Pongo core remains free under AGPLv3/SSPL. No gatekeeping for regular users.&lt;/p&gt;
&lt;h3 id=&quot;paid-products&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#paid-products&quot; aria-label=&quot;paid products permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Paid Products&lt;/h3&gt;
&lt;p&gt;To fund development and make Emmett and Pongo sustainable, I plan to provide additional paid products and services. For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Advanced tooling for observability, troubleshooting and monitoring,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Web UI for project documentation, messages visualization,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Advanced multitenancy, security and other enterpise features,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Additional integrations with Cloud/AI/ML tooling.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The separation clarifies responsibilities. Open source means community-supported. Paid means I’m accountable for maintenance and support.&lt;/p&gt;
&lt;p&gt;I also want to offer support services and paid features prioritisation. But that can come under the regular contract terms.&lt;/p&gt;
&lt;h2 id=&quot;personal-context&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#personal-context&quot; aria-label=&quot;personal context permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Personal Context&lt;/h2&gt;
&lt;p&gt;I maintained Marten for years. Despite adoption and success, I couldn’t make it sustainable. Good intentions don’t pay bills.&lt;/p&gt;
&lt;p&gt;With Emmett and Pongo, I’ve been transparent from the start: these need to be sustainable for me to focus on them. Keeping Emmett unlicensed was deliberate, avoiding the permissive-to-restrictive transition that breaks trust.&lt;/p&gt;
&lt;p&gt;Now, with meaningful community contributions, I’m formalising what I’ve always intended: a structure that’s fair to contributors while enabling sustainable development.&lt;/p&gt;
&lt;h2 id=&quot;risks-and-trade-offs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#risks-and-trade-offs&quot; aria-label=&quot;risks and trade offs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Risks and Trade-offs&lt;/h2&gt;
&lt;h3 id=&quot;fork-risk&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#fork-risk&quot; aria-label=&quot;fork risk permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Fork Risk&lt;/h3&gt;
&lt;p&gt;Cloud providers may fork rather than negotiate. Valkey shows this is real. But maintaining forks requires resources and community work. They will need at least expose it as an open source, so we could get the improvements back..&lt;/p&gt;
&lt;h3 id=&quot;adoption-barriers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#adoption-barriers&quot; aria-label=&quot;adoption barriers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Adoption Barriers&lt;/h3&gt;
&lt;p&gt;Some organisations prohibit AGPL or SSPL. The dual approach maximises compatibility, but some potential users will be lost. Better sustainable development with narrower adoption than broad adoption of an abandoned project.&lt;/p&gt;
&lt;h3 id=&quot;complexity&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#complexity&quot; aria-label=&quot;complexity permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Complexity&lt;/h3&gt;
&lt;p&gt;Two licenses can create confusion. Clear documentation and decision trees help, but they add friction. That’s also why we’re doing it transparently as an RFC.&lt;/p&gt;
&lt;h3 id=&quot;trust&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#trust&quot; aria-label=&quot;trust permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Trust&lt;/h3&gt;
&lt;p&gt;Even with transparency from the start, formalising licenses after contributions creates tension. The CLA process may deter some contributors.&lt;/p&gt;
&lt;h2 id=&quot;faq&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#faq&quot; aria-label=&quot;faq permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;FAQ&lt;/h2&gt;
&lt;h3 id=&quot;can-i-choose-either-license-for-any-use-case&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#can-i-choose-either-license-for-any-use-case&quot; aria-label=&quot;can i choose either license for any use case permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Can I choose either license for any use case?&lt;/h3&gt;
&lt;p&gt;Yes. You may choose to comply with either AGPLv3 or SSPL. You only need to comply with one license. The choice depends on your use case, compliance requirements, and comfort with the license terms.&lt;/p&gt;
&lt;h3 id=&quot;do-i-have-to-open-source-my-entire-application-if-i-use-emmett-or-pongo-under-agplv3&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#do-i-have-to-open-source-my-entire-application-if-i-use-emmett-or-pongo-under-agplv3&quot; aria-label=&quot;do i have to open source my entire application if i use emmett or pongo under agplv3 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Do I have to open source my entire application if I use Emmett or Pongo under AGPLv3?&lt;/h3&gt;
&lt;p&gt;No. AGPLv3 requires you to open source modifications to Emmett or Pongo themselves if users interact with those modified versions over a network. Your application logic, domain models, and other services remain yours. You are not required to open source your entire stack.&lt;/p&gt;
&lt;h3 id=&quot;can-i-write-plug-ins-or-extensions-to-emmettpongo-and-keep-them-proprietary&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#can-i-write-plug-ins-or-extensions-to-emmettpongo-and-keep-them-proprietary&quot; aria-label=&quot;can i write plug ins or extensions to emmettpongo and keep them proprietary permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Can I write plug-ins or extensions to Emmett/Pongo and keep them proprietary?&lt;/h3&gt;
&lt;p&gt;Yes, if your plug-ins are separate modules that use public interfaces and do not modify the Emmett/Pongo codebase directly, you may keep them proprietary. However, if your extension modifies core internals or you redistribute a modified version, the license obligations may apply.&lt;/p&gt;
&lt;h3 id=&quot;why-offer-sspl-if-agplv3-already-provides-protection&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-offer-sspl-if-agplv3-already-provides-protection&quot; aria-label=&quot;why offer sspl if agplv3 already provides protection permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why offer SSPL if AGPLv3 already provides protection?&lt;/h3&gt;
&lt;p&gt;SSPL makes the service-use restrictions more explicit and comprehensive. It removes ambiguity around what counts as a modification, and helps organisations that want stronger language around SaaS offerings without worrying about copyleft infecting unrelated application code.&lt;/p&gt;
&lt;h3 id=&quot;what-does-sspl-require-that-agplv3-does-not&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-does-sspl-require-that-agplv3-does-not&quot; aria-label=&quot;what does sspl require that agplv3 does not permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What does SSPL require that AGPLv3 does not?&lt;/h3&gt;
&lt;p&gt;SSPL extends the copyleft obligation: if you offer Emmett or Pongo as a service to third parties, you must open source not only modifications to Emmett or Pongo, but all the infrastructure code used to run that service—monitoring, orchestration, authentication, deployment scripts, UI dashboards, etc. For internal use or building apps with Emmett/Pongo, SSPL behaves like AGPLv3.&lt;/p&gt;
&lt;h3 id=&quot;what-counts-as-a-modification-under-agplv3-and-sspl&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-counts-as-a-modification-under-agplv3-and-sspl&quot; aria-label=&quot;what counts as a modification under agplv3 and sspl permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What counts as a “modification” under AGPLv3 and SSPL?&lt;/h3&gt;
&lt;p&gt;A modification includes direct changes to Emmett or Pongo source code - e.g., altering the event store engine, changing how projections work, or rewriting part of the message pipeline. Plug-ins or separate modules that interact with Emmett/Pongo through stable interfaces are generally not considered modifications, but consult legal counsel if uncertain.&lt;/p&gt;
&lt;h3 id=&quot;is-this-open-source&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#is-this-open-source&quot; aria-label=&quot;is this open source permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Is this open source?&lt;/h3&gt;
&lt;p&gt;Yes. AGPLv3 is an OSI-approved open source license. SSPL is source-available but not OSI-approved. The project remains open for use, modification, and distribution under AGPLv3.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This dual licensing addresses goals I’ve had from the beginning: creating sustainable development conditions while keeping projects accessible to everyone but also preventing my (and other contributors)’) work from exploitation.&lt;/p&gt;
&lt;p&gt;Will it work? MongoDB protected itself but faces ongoing community friction. Elastic and Redis found a middle path but spawned major forks. Projects staying permissively licensed often struggle with sustainability or watch cloud providers profit from their work.&lt;/p&gt;
&lt;p&gt;I’m choosing to formalise now what I’ve always planned, with community input. The alternative—continuing without proper licensing or choosing permissive licenses—guarantees either abandonment or exploitation.&lt;/p&gt;
&lt;p&gt;This isn’t about getting rich or rug pull. It’s about creating conditions where these projects can survive in the long term, serving users and enabling me to get the full focus on them. This is the intended win-win for both sides.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I hope that this summary will also be helpful for you, even if you’re not planning to use Emmett or Pongo, and it will explain to you a bit more on the current OSS landscape.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can comment either here or through the RFC published in the Emmett repository: &lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/260&quot;&gt;https://github.com/event-driven-io/emmett/pull/260&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Feel also invited to join our &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Discord Channel&lt;/a&gt; if you prefer chat.&lt;/p&gt;
&lt;p&gt;Thank you in advance!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Workflow Engine design proposal, tell me your thoughts]]></title><description><![CDATA[Welcome to the new week! As you may know, I’m building Emmett - a library/framework aiming to make building business-focused apps smoother…]]></description><link>https://event-driven.io/en/workflow-engine-design-proposal-tell/</link><guid isPermaLink="false">https://event-driven.io/en/workflow-engine-design-proposal-tell/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;As you may know, I’m building &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt; - a library/framework aiming to make building business-focused apps smoother with an event-driven approach. It’s a community project, and with community projects, sharing is caring, so I’m sharing today with you an idea, hoping that you’ll also share something with me: your thoughts on it.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#summary&quot; aria-label=&quot;summary permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Summary&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;We’re adding a workflow processing engine to Emmett for coordinating multi-step/asynchronous business processes.&lt;/strong&gt; It uses an event store behind the scenes to provide durable execution with full observability, treating each workflow instance as an event stream that acts as both inbox and outbox for handled messages.&lt;/p&gt;
&lt;h2 id=&quot;problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#problem&quot; aria-label=&quot;problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Problem&lt;/h2&gt;
&lt;p&gt;Multi-step processes are everywhere in business applications, not just the typical order-fulfilment examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Hotel group checkout coordinating multiple room settlements,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Code review workflow with multiple approvers and checks,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Customer onboarding with verification steps,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Incident response coordination across teams,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Data ingestion with validation and enrichment stages.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-durable-execution-matters&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-durable-execution-matters&quot; aria-label=&quot;why durable execution matters permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Durable Execution Matters&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;These processes need to survive more than we often realise.&lt;/strong&gt; A group checkout may be a relatively quick batch operation, but a document review workflow can span days with human approvals. An incident response could run for hours, coordinating different teams.&lt;/p&gt;
&lt;p&gt;When things fail, and they will, we need to know exactly where we stopped and why. A network blip shouldn’t cause an in-progress checkout to be dropped in the middle. A service restart shouldn’t forget which steps it passed. A bug fix shouldn’t require manually tracking down stuck processes.&lt;/p&gt;
&lt;p&gt;Of course, you could implement your own processing based on the existing abstractions in Emmett, like command handlers, consumers and async processors, but…&lt;/p&gt;
&lt;p&gt;DIY solutions always have gaps. We handle the happy path, add some retry logic, then discover edge cases in production:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Messages lost during deployment&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Processes stuck after partial failures&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No way to resume after fixing bugs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Can’t answer “what happened to order X?”&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;current-tools-work-but-bring-complexity&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#current-tools-work-but-bring-complexity&quot; aria-label=&quot;current tools work but bring complexity permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Current Tools Work But Bring Complexity&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://temporal.io/&quot;&gt;Temporal&lt;/a&gt;, &lt;a href=&quot;https://restate.dev/&quot;&gt;Restate&lt;/a&gt;, &lt;a href=&quot;https://aws.amazon.com/step-functions/&quot;&gt;AWS Step Functions&lt;/a&gt;, and similar tools solve these problems.&lt;/strong&gt; They guarantee processes are complete despite failures. They provide visibility into process state. &lt;strong&gt;But they come with cognitive overhead.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To write a simple approval workflow in Temporal, you learn about activities, workflows, workers, task queues, and their deterministic replay requirements. Your “send email for approval” becomes an activity with retry policies, timeouts, and heartbeats. Testing requires their test server, time-skipping APIs, and mock activities.&lt;/p&gt;
&lt;p&gt;These tools require learning their entire programming model, even for simple workflows. You get powerful features, but pay the complexity cost upfront, whether you need those features or not.&lt;/p&gt;
&lt;h3 id=&quot;what-we-actually-need&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-we-actually-need&quot; aria-label=&quot;what we actually need permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What We Actually Need&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;We need durable execution that feels natural. Something that allows just writing regular code.&lt;/strong&gt; Testing should be just calling functions. The patterns should match how we already think about our domain. Simple workflows should be simple to write, with complexity available when needed. Therefore, we need a model that reuses and repeats patterns, allowing for the learning ladder. This can be achieved by providing safe defaults and extension points for advanced scenarios. In other words, an accessible model enabling both newbies and advanced users.&lt;/p&gt;
&lt;h2 id=&quot;background-event-sourcing-as-natural-solution&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#background-event-sourcing-as-natural-solution&quot; aria-label=&quot;background event sourcing as natural solution permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Background: Event Sourcing as Natural Solution&lt;/h2&gt;
&lt;h3 id=&quot;what-makes-execution-durable&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-makes-execution-durable&quot; aria-label=&quot;what makes execution durable permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What Makes Execution Durable&lt;/h3&gt;
&lt;p&gt;At its core, durable execution means three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Persist progress at each decision point&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Resume from the last known state after failures&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Know exactly what happened and why&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Traditional approaches use state machines with database tables, workflow engines with proprietary storage, or complex checkpoint mechanisms. But there’s a more straightforward way.&lt;/p&gt;
&lt;h3 id=&quot;event-sourcing-built-in-durability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#event-sourcing-built-in-durability&quot; aria-label=&quot;event sourcing built in durability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Event Sourcing: Built-in Durability&lt;/h3&gt;
&lt;p&gt;Event sourcing stores every business decision as an immutable event. For workflows, this means every input received and output produced becomes an event in a stream. This isn’t just storage - it’s the recovery mechanism. We can expand this model and use the event store as the message store, storing all messages. This allows us to store both commands (intention to run business logic) and events (facts about what has happened, which can be used as triggers for next steps in the workflow).&lt;/p&gt;
&lt;p&gt;Consider the following scenario:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Clerk initiating the group checkout for two guest stays, let’s say with ids 1 and 2.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We need to record that the group checkout was initiated and start regular checkout processes for each guest stay.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then we need to gather information based on successful or failed checkouts and complete the group checkout.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With our workflow design, the message stream with each recorded message representing a step in the workflow can look as follows:&lt;/p&gt;
&lt;p&gt;1: InitiateGroupCheckout command handled.&lt;/p&gt;
&lt;p&gt;2: GroupCheckoutInitiated event recorded for guest stays 1 and 2.&lt;/p&gt;
&lt;p&gt;3: CheckOut command scheduled, initiating checkout for guest stay 1.&lt;/p&gt;
&lt;p&gt;4: CheckOut command scheduled, initiating checkout for guest stay 2.&lt;/p&gt;
&lt;p&gt;Then, the background process can poll those messages and sequentially process them, storing the checkpoints after successful processing.&lt;/p&gt;
&lt;p&gt;Now, we could get the next step:&lt;/p&gt;
&lt;p&gt;5: GuestCheckedOut event received informing about successful checkout for guest stay 1.&lt;/p&gt;
&lt;p&gt;6. Process crashes, and the second checkout was not processed.&lt;/p&gt;
&lt;p&gt;That’s not an issue, as upon restart, we can&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Read the last successful checkpoint (which happens to be 3).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Continue processing triggering Checkout for guest stay 2.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No special recovery code needed&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The workflow doesn’t know it crashed. It just continues from where it left off.&lt;/p&gt;
&lt;h3 id=&quot;why-event-sourcing-makes-workflows-simple&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-event-sourcing-makes-workflows-simple&quot; aria-label=&quot;why event sourcing makes workflows simple permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Event Sourcing Makes Workflows Simple&lt;/h3&gt;
&lt;p&gt;Beyond recovery, event sourcing brings natural benefits to workflows:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Time Travel Debugging&lt;/strong&gt;: When a workflow fails, replay event-by-event to see the exact decision sequence. No more “I wonder what state it was in.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What-If Analysis&lt;/strong&gt;: Fix a bug and replay to see if workflows would complete correctly. Test process changes against historical data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Natural Audit Trail&lt;/strong&gt;: Every decision is recorded with its trigger. Compliance and debugging come for free.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Error Analysis&lt;/strong&gt;: We could also store errors as messages. See patterns across failed workflows. Understand systemic issues.&lt;/p&gt;
&lt;p&gt;Emmett already provides these event sourcing primitives. We have event stores that handle both events and commands (message stores). We have the decide/evolve pattern for business logic. We have consumers for background processing. What’s missing is the orchestration pattern to coordinate across multiple streams with proper correlation. That’s what workflows add.&lt;/p&gt;
&lt;h2 id=&quot;solution-overview&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#solution-overview&quot; aria-label=&quot;solution overview permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Solution Overview&lt;/h2&gt;
&lt;p&gt;Workflows in Emmett follow the same mental model as &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html#command-handling&quot;&gt;command handlers&lt;/a&gt;: they receive commands, make decisions based on state, and produce new messages. But unlike command handlers that only respond to commands, workflows can react to events from anywhere in your system.&lt;/p&gt;
&lt;p&gt;The pattern has three functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;decide&lt;/code&gt;: Business logic that takes input + state and returns outputs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;evolve&lt;/code&gt;: Builds state based on events (both inputs and outputs)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;initialState&lt;/code&gt;: Starting state for new workflow instances&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each workflow instance gets its own event stream. This stream serves as both an inbox (recording inputs) and an outbox (recording outputs). This self-contained design means everything about a workflow instance lives in one place.&lt;/p&gt;
&lt;p&gt;This design draws huge inspiration from &lt;a href=&quot;https://blog.bittacklr.be/the-workflow-pattern.html&quot;&gt;Yves Reynhout’s Workflow Pattern&lt;/a&gt;, adapting it to use Emmett’s abstractions while aiming to retain its power.&lt;/p&gt;
&lt;p&gt;Check also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/256&quot;&gt;Pull Request with sample Emmett’s Workflow based on this RFC&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how_to_have_fun_with_typescript_and_workflow/&quot;&gt;My earlier article on implementing Workflow Pattern in TypeScript&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/inmemory_message_bus_in_typescript/&quot;&gt;My article on message bus and message handling that inspired some of this design&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-it-works---technical-design&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-it-works---technical-design&quot; aria-label=&quot;how it works   technical design permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How it Works - Technical Design&lt;/h2&gt;
&lt;h3 id=&quot;processing-flow&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#processing-flow&quot; aria-label=&quot;processing flow permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Processing Flow&lt;/h3&gt;
&lt;p&gt;When a message triggers a workflow, we use a double-hop pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Input message arrives&lt;/strong&gt; from source stream (event or command).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Router function&lt;/strong&gt; (&lt;code class=&quot;language-text&quot;&gt;getWorkflowId&lt;/code&gt;) determines which workflow instance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Store input&lt;/strong&gt; in the workflow’s stream (as inbox).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consumer delivers&lt;/strong&gt; the stored message from the workflow stream&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rebuild state&lt;/strong&gt; by replaying all events through &lt;code class=&quot;language-text&quot;&gt;evolve&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Call decide&lt;/strong&gt; with the input and current state.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Store outputs&lt;/strong&gt; back in the workflow stream (as outbox).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Output processor&lt;/strong&gt; sends commands and publishes events.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This flow ensures durability at each step:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/691c599a4c634d7edc3c1c5920c45f21/ae024/2025-07-28-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABhElEQVR42n2SiY6cMBBE5/8/MIq0ymZzDDAefGCDLzCVatidREomLbUwpnldZfclmYIaGiSWvWHMEWNJKPu5t0uuBdkNSJPCNmu4XPBqPd6YYd1Y76CigV8XXNwQYAePnApMXPDdGOicUPcdH3ECbwi6Y/MRUyn4Mlq8mQnztuFOWMdmvhK4LBHaOBimcxOsnbCxqNYVkw/wzHVdj70Y06NJJlT2JLbWkKla4iKL1tqjMKV8wCohmWt5L6USltnQHylNQpjZ6AlQOu3vFkWFFM/zgvvdQKnxyFFb9L2C5lO+Wbp5CvxTYaQisS8Ard0BEPAHTNSfluszy/kvy41q9/cblpDvicq7bqBdWiVIAA+F2//OkA3EvpzA73s+f7IcE8lAqOQ/Lc9jRAmnjcTZ671HP00IVCRqI8cpLjN09w3X1xd41cFyMl5uGl85OlaUO4WrvSO2jEu4RURdD6BvK66zx5CWx2Aflivh+gei6bBNN2hCPimDz4pDzjPt44ifXsGUgF9xkQiORki1+AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/691c599a4c634d7edc3c1c5920c45f21/507e6/2025-07-28-cover.webp 200w,
/static/691c599a4c634d7edc3c1c5920c45f21/28a80/2025-07-28-cover.webp 400w,
/static/691c599a4c634d7edc3c1c5920c45f21/8d2ea/2025-07-28-cover.webp 800w,
/static/691c599a4c634d7edc3c1c5920c45f21/8adb5/2025-07-28-cover.webp 1034w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/691c599a4c634d7edc3c1c5920c45f21/36ca5/2025-07-28-cover.png 200w,
/static/691c599a4c634d7edc3c1c5920c45f21/a3397/2025-07-28-cover.png 400w,
/static/691c599a4c634d7edc3c1c5920c45f21/a331c/2025-07-28-cover.png 800w,
/static/691c599a4c634d7edc3c1c5920c45f21/ae024/2025-07-28-cover.png 1034w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/691c599a4c634d7edc3c1c5920c45f21/a331c/2025-07-28-cover.png&quot;
            alt=&quot;2025 07 28 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;code-example---group-checkout&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-example---group-checkout&quot; aria-label=&quot;code example   group checkout permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Code Example - Group Checkout&lt;/h3&gt;
&lt;p&gt;Let’s see how this works with a group checkout workflow that coordinates multiple individual checkouts:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; GroupCheckoutWorkflow&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Workflow
 GroupCheckoutInput&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 GroupCheckout&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 GroupCheckoutOutput
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
 decide&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The state tracks which guests have checked out:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GroupCheckout&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotExisting&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Pending&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      guestStayAccountIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; GuestStayStatus&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Finished&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The decide function contains our business logic:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; decide &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  input&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GroupCheckoutInput&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GroupCheckout&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WorkflowOutput&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;GroupCheckoutOutput&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;InitiateGroupCheckout&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initiateGroupCheckout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckoutFailed&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onCheckoutFinished&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;TimeoutGroupCheckout&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;timedOut&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and state evolution:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; evolve &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GroupCheckout&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WorkflowEvent&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;GroupCheckoutInput &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; GroupCheckoutOutput&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GroupCheckout &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GroupCheckoutInitiated&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotExisting&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Pending&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        guestStayAccountIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;guestStayAccountIds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; map&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; GuestStayStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Pending&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; GuestStayStatus&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckoutFailed&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Pending&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        guestStayAccountIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;guestStayAccountIds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckedOut&apos;&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; GuestStayStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Completed
            &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayStatus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Failed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GroupCheckoutCompleted&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GroupCheckoutFailed&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GroupCheckoutTimedOut&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Pending&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Finished&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We register it with routing and type information:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; groupCheckoutWorkflowProcessor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;workflowProcessor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  processorId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GroupCheckoutWorkflow&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  workflow&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GroupCheckoutWorkflow&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;getWorkflowId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; input&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;groupCheckoutId &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  inputs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    commands&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;InitiateGroupCheckout&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;TimeoutGroupCheckout&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckoutFailed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  outputs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    commands&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;CheckOut&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;GroupCheckoutCompleted&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;GroupCheckoutFailed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;GroupCheckoutTimedOut&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Inputs and outputs have to be registered explicitly. We cannot detect them from types, as they’re erased on the JavaScript runtime. Still, they will have a proper intellisense and get compiler help, not allowing them to register more types than allowed.&lt;/p&gt;
&lt;p&gt;We’re having an explicit registration for commands and events to make API explicit, and also not require additional wrapper functions in the &lt;code class=&quot;language-text&quot;&gt;decide&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;See the full usage sample:&lt;/p&gt;
&lt;h3 id=&quot;stream-structure&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#stream-structure&quot; aria-label=&quot;stream structure permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Stream Structure&lt;/h3&gt;
&lt;p&gt;The workflow’s stream name comes from the &lt;code class=&quot;language-text&quot;&gt;getWorkflowId&lt;/code&gt; function. Here’s what the stream contains as the workflow executes:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Pos |  Kind   | Direction | Message
----|---------|-----------|------------------------------------------
1   | Command | Input     | InitiateGroupCheckout {groupId: &apos;123&apos;}
2   | Event   | Output    | GroupCheckoutInitiated
3   | Command | Output    | CheckOut {guestId: &apos;g1&apos;}
4   | Command | Output    | CheckOut {guestId: &apos;g2&apos;}
5   | Command | Output    | CheckOut {guestId: &apos;g3&apos;}
6   | Event   | Input     | GuestCheckedOut {guestId: &apos;g1&apos;}
7   | Event   | Input     | GuestCheckoutFailed {guestId: &apos;g2&apos;}
8   | Event   | Input     | GuestCheckedOut {guestId: &apos;g3&apos;}
9   | Event   | Output    | GroupCheckoutFailed&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice how inputs and outputs interleave. The workflow receives inputs (positions 1, 6, 7, 8) and produces outputs (positions 2, 3, 4, 5, 9). Inputs can be either commands sent sequentially from the API, other message processing, or events recorded in regular streams.&lt;/p&gt;
&lt;p&gt;Emmett’s event store, which is actually a message store that tracks both events and commands with their types.&lt;/p&gt;
&lt;p&gt;This single stream contains the complete story, including what triggered each decision and what the workflow ultimately decided. No distributed tracing needed.&lt;/p&gt;
&lt;h2 id=&quot;why-this-design&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-this-design&quot; aria-label=&quot;why this design permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why This Design&lt;/h2&gt;
&lt;h3 id=&quot;actor-model-self-contained-instances&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#actor-model-self-contained-instances&quot; aria-label=&quot;actor model self contained instances permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Actor Model: Self-Contained Instances&lt;/h3&gt;
&lt;p&gt;Each workflow instance acts like an actor with its own mailbox:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8012340f8d9eb8faaf2096743102d1f9/4e032/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 39.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABN0lEQVR42oVSa2+DMAzk///GbrSDUiAJzwB5wc12Ww3tw4Z0cuRczmeb7ADwBn/LusHaFXbZsBC2zWGlXNMqaG1gTIeuG4TD9yvdc7T2ec5+C47DTAQW8STm0fcjyqLCNS9wu5Woqhp5/oX6oXAvH1RgEJ5zAeM4PwVxEpznBSEkpHRgmuwLi0R25X2UIsMwwZBT5rNjR/mZOFlMCT4ExBhJJFHFnggjtdeTi1aqTtNM0crjttVoGoWmVnJWqsPlkqO619D0NtO6k1YKaotnxO2VxYNaa165Tmb0dsgjYUfsksGF2QTPkF1nx3FgZ+y7tCzuDD+YoBUXK0SUySzAYmdwTpazup8ZnpfiHQ2YtuUoBh+IvOLz4yrLUMrIxvnuDBbjvCd+hn++ECIMOeRxtPTrxJj+5H8DLK5oLDipxWcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/8012340f8d9eb8faaf2096743102d1f9/507e6/image-2.webp 200w,
/static/8012340f8d9eb8faaf2096743102d1f9/28a80/image-2.webp 400w,
/static/8012340f8d9eb8faaf2096743102d1f9/8d2ea/image-2.webp 800w,
/static/8012340f8d9eb8faaf2096743102d1f9/6472e/image-2.webp 1119w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/8012340f8d9eb8faaf2096743102d1f9/36ca5/image-2.png 200w,
/static/8012340f8d9eb8faaf2096743102d1f9/a3397/image-2.png 400w,
/static/8012340f8d9eb8faaf2096743102d1f9/a331c/image-2.png 800w,
/static/8012340f8d9eb8faaf2096743102d1f9/4e032/image-2.png 1119w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/8012340f8d9eb8faaf2096743102d1f9/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This isolation brings several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No shared state&lt;/strong&gt;: Each workflow instance has its own stream&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Horizontal scaling&lt;/strong&gt;: Process many workflows in parallel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clear boundaries&lt;/strong&gt;: Easy to reason about each instance&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Natural sharding&lt;/strong&gt;: Distribute by workflow ID&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;observable-by-design&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#observable-by-design&quot; aria-label=&quot;observable by design permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Observable by Design&lt;/h3&gt;
&lt;p&gt;Everything about a workflow lives in one stream. Want to know why a checkout failed? Read the stream. Need to debug a stuck process? The entire history is there. This isn’t observability bolted on - it’s fundamental to how workflows work. When connected to OpenTelemetry, it should provide better visibility into business processes than regular solutions. Knowledge about abstractions like commands and events can provide durable context, not only for error debugging, but also for classical telemetry.&lt;/p&gt;
&lt;h3 id=&quot;durable-by-default&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#durable-by-default&quot; aria-label=&quot;durable by default permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Durable by Default&lt;/h3&gt;
&lt;p&gt;Recovery isn’t a special case - it’s just reading the stream and rebuilding state. No checkpointing logic. The same code path handles normal processing and crash recovery. If we want to replay messages, we can just replay processing from a specific stream position.&lt;/p&gt;
&lt;h3 id=&quot;simple-testing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#simple-testing&quot; aria-label=&quot;simple testing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Simple Testing&lt;/h3&gt;
&lt;p&gt;Workflows are pure functions. Testing them is straightforward:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Unit test&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Pending&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; guestStayAccountIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; guestId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;g1&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; outputs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decide&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Assert expected outputs&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can test it without workflow runtime using regular tests, or built-in Emmett Behaviour-Driven Design testing capabilities, e.g.:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;handles partial failures&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GroupCheckoutInitiated&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; guestIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;g1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;g2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; guestId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;g1&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckoutFailed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; guestId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;g2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;BalanceNotSettled&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GroupCheckoutFailed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; completedCheckouts&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;g1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; failedCheckouts&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;g2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Read more in: &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html#unit-testing&quot;&gt;Unit testing docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A similar pattern can be used for a full integration test.&lt;/p&gt;
&lt;p&gt;No special, external workflow runtime is needed to test the workflow code. Compare this to other tools where you need test servers, activity mocks, and time manipulation. Emmett workflows are just functions processing messages.&lt;/p&gt;
&lt;h3 id=&quot;why-emmett-over-established-tools&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-emmett-over-established-tools&quot; aria-label=&quot;why emmett over established tools permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Emmett Over Established Tools&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Familiar Patterns&lt;/strong&gt;: If you know Emmett’s command handlers, you know workflows. Same decide/evolve pattern, just triggered by events too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Progressive Complexity&lt;/strong&gt;: Start with a simple workflow. Add timeout handling when needed. Add compensation if required. You don’t front-load complexity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lightweight Runtime&lt;/strong&gt;: No separate workflow server. No special deployment model. Just your application with background processors.&lt;/p&gt;
&lt;h2 id=&quot;design-tradeoffs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#design-tradeoffs&quot; aria-label=&quot;design tradeoffs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Design Tradeoffs&lt;/h2&gt;
&lt;h3 id=&quot;latency-the-double-hop-cost&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#latency-the-double-hop-cost&quot; aria-label=&quot;latency the double hop cost permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Latency: The Double-Hop Cost&lt;/h3&gt;
&lt;p&gt;Storing inputs before processing adds latency. A message goes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Source → Workflow stream (store)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Workflow stream → Processor (execute)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For background processes, this extra hop is negligible compared to the business operations. For latency-sensitive operations, use regular command handlers instead.&lt;/p&gt;
&lt;p&gt;Future versions could make this configurable, allowing for direct processing for low-latency needs and a double-hop approach for full observability. We’d need community feedback on whether this flexibility matters.&lt;/p&gt;
&lt;h3 id=&quot;storage-everything-twice&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#storage-everything-twice&quot; aria-label=&quot;storage everything twice permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Storage: Everything Twice&lt;/h3&gt;
&lt;p&gt;Messages exist in both source streams and workflow streams. A &lt;code class=&quot;language-text&quot;&gt;GuestCheckedOut&lt;/code&gt; event appears in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The guest’s stream (source of truth)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The workflow’s stream (for processing)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This duplication is intentional. Different retention policies can help - keep workflow streams shorter since they’re derived data. The observability benefit outweighs the storage cost for most applications.&lt;/p&gt;
&lt;h3 id=&quot;when-not-to-use-workflows&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#when-not-to-use-workflows&quot; aria-label=&quot;when not to use workflows permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;When Not to Use Workflows&lt;/h3&gt;
&lt;p&gt;Workflows aren’t for everything:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simple request-response&lt;/strong&gt;: Use command handlers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Read models creation&lt;/strong&gt;: Use projections.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simple message handling:&lt;/strong&gt; Use reactors, they’re simple enough for basic choreography,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Synchronous operations&lt;/strong&gt;: Workflows can handle them, but for most cases, &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html#command-handling&quot;&gt;command handlers&lt;/a&gt; are simpler.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Workflows shine for multi-step, long-running processes that coordinate across streams.&lt;/p&gt;
&lt;p&gt;This design is based on Yves Reynhout’s Workflow Pattern, which has proven itself in production systems. We’ve adapted it to fit naturally with Emmett’s abstractions.&lt;/p&gt;
&lt;h2 id=&quot;future-possibilities&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#future-possibilities&quot; aria-label=&quot;future possibilities permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Future Possibilities&lt;/h2&gt;
&lt;h3 id=&quot;other-workflow-patterns&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#other-workflow-patterns&quot; aria-label=&quot;other workflow patterns permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Other Workflow Patterns&lt;/h3&gt;
&lt;p&gt;The current design provides a simpler alternative to traditional process managers. We could add Saga as a stateless router with compensation logic and simple event handlers for choreography.&lt;/p&gt;
&lt;p&gt;These would build on the similar foundation.&lt;/p&gt;
&lt;h3 id=&quot;processing-mode-flexibility&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#processing-mode-flexibility&quot; aria-label=&quot;processing mode flexibility permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Processing Mode Flexibility&lt;/h3&gt;
&lt;p&gt;Currently, we always store inputs first for full observability. Some workflows might prefer lower latency:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Full observability mode&lt;/strong&gt;: Current double-hop design&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Direct mode&lt;/strong&gt;: Process immediately, only store outputs&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hybrid&lt;/strong&gt;: Configure per message type&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This needs careful design to maintain durability guarantees.&lt;/p&gt;
&lt;h3 id=&quot;enrichment-patterns&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#enrichment-patterns&quot; aria-label=&quot;enrichment patterns permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Enrichment Patterns&lt;/h3&gt;
&lt;p&gt;Workflows may sometimes need external data. How should they fetch it deterministically?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 1 - Pre-enrichment&lt;/strong&gt;: Enrich at system edges before the workflow sees the message.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 2 - Enrichment Step&lt;/strong&gt;: Dedicated enrichment workflows that add data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 3 - Request/Response&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Workflow → FetchPrices query → Wait for Prices result.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The same can be used for external communication, and also for output enrichment/transformation.&lt;/p&gt;
&lt;p&gt;Each has different tradeoffs for testing, performance, and complexity. We need user feedback.&lt;/p&gt;
&lt;h3 id=&quot;messaging-system-support&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#messaging-system-support&quot; aria-label=&quot;messaging system support permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Messaging System Support&lt;/h3&gt;
&lt;p&gt;Today, inputs come from event stores. Tomorrow they could also come from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Kafka topics,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RabbitMQ queues,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;SQS messages,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Supabase queues,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The workflow doesn’t care about the source. Streams remain in the event store for durability.&lt;/p&gt;
&lt;h3 id=&quot;error-handling-evolution&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#error-handling-evolution&quot; aria-label=&quot;error handling evolution permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Error Handling Evolution&lt;/h3&gt;
&lt;p&gt;Future versions could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Store errors as events for analysis&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure retry policies per workflow&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dead letter handling for poison messages&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Timeout escalation strategies&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;paid-tools-making-emmett-sustainable&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#paid-tools-making-emmett-sustainable&quot; aria-label=&quot;paid tools making emmett sustainable permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Paid Tools (Making Emmett Sustainable)&lt;/h3&gt;
&lt;p&gt;I want to make Emmett sustainable through optional paid tooling:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Visual Workflow Debugger&lt;/strong&gt;: Step through workflow execution with time travel. See state at each decision point. Understand complex flows visually.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Business Process Analytics&lt;/strong&gt;: How long do checkouts take? Where do they fail? Which steps are bottlenecks? Dashboard for business users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;State Diagram Generation&lt;/strong&gt;: Generate visual documentation from running workflows. See all possible paths. Spot missing transitions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Performance Analysis&lt;/strong&gt;: Which workflows consume most resources? Where are the hot paths? Optimisation recommendations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Documentation Generation&lt;/strong&gt;: Turn workflow definitions into readable docs. Keep business and code in sync.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Question for the community&lt;/strong&gt;: Would these tools provide enough value to justify payment? This would fund continued Emmett development while keeping the core open source.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This workflow engine brings durable execution to Emmett using patterns developers already know. It allows you to just write TypeScript code. Event sourcing provides durability and observability naturally, not as add-ons, but as fundamental properties.&lt;/p&gt;
&lt;p&gt;We avoid the complexity of existing tools while keeping power available when needed. Simple workflows stay simple. Complex workflows become possible. Testing remains straightforward. We want to compose, avoiding vendor locking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Together, we can build workflows that are both powerful and pleasant to use. We’re really open to your questions, suggestions and constructive criticism.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We’d also like to know if the additional paid add-ons mentioned are something you would consider paying for.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can comment either here or through the RFC published in the Emmett repository: &lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/257&quot;&gt;https://github.com/event-driven-io/emmett/pull/257&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Feel also invited to join our &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Discord Channel&lt;/a&gt; if you prefer chat.&lt;/p&gt;
&lt;p&gt;Thank you in advance!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. that’s also an example of what I meant in last e-mail that I plan to share more explicitly on stuff I do.&lt;/p&gt;
&lt;p&gt;p.s.2. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Order of Things: Why You Can't Have Both Speed and Ordering in Distributed Systems]]></title><description><![CDATA[Picture this scenario: An online retailer runs a flash sale with 500 units of a hot product. Within minutes, they’ve processed 742 orders…]]></description><link>https://event-driven.io/en/the-order-of-things-why-you-cant/</link><guid isPermaLink="false">https://event-driven.io/en/the-order-of-things-why-you-cant/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Picture this scenario: An online retailer runs a flash sale with 500 units of a hot product.&lt;/strong&gt; Within minutes, they’ve processed 742 orders. Their inventory shows -242 units. Customer service is about to have a very bad day.&lt;/p&gt;
&lt;p&gt;This isn’t a story about bad code. The engineers did everything right: database transactions, inventory checks, proper error handling. They discovered that in distributed systems, you must choose between global ordering and performance.&lt;/p&gt;
&lt;p&gt;When you have multiple servers processing requests simultaneously, “check inventory then charge customer” becomes a race condition. The milliseconds between “check” and “charge” are windows where other orders slip through.&lt;/p&gt;
&lt;p&gt;You might think stronger locks could fix this. They could, while destroying your system’s performance and/or its availability. Let’s discuss today why.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 429px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5862c629900cfc8f24e8a8527098812b/2b666/2025-07-14-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 111.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAIAAABPIytRAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEvElEQVR42l1R2VNadxS+f0JfMtOZNpO3Tt/St/StL+lMM20zTWoSxTVpKggKiYomLohsoqAsIosguCGIsmpMQJFNFNTEKLgFUSAETROTNC4pew9m+tLffHPP9/vO+c45915EbV0bfLysmnmudW6M2dcBWuemxuaH66jNp55dy5FZn9q6qppZHXdsnGEdFABimN/hKC1Ycg+xQ8GUT9R3DmKauxkyI5DKVhGuVUiV6DCk7mqGrIYpp/casC099+jSXsO82uZH2vpMjVwlJNiDj/kqKzSiirUSvQuunGELtAO0Kyabu9XsgakejZ01MEWgSST6uVEwlxI7mvnq6+hmaFnexKuk5Kb92cjFU8UFeJpY64Klyois8kbeHw+6qiii4mpmSU07KKTuUQRm1rEGGjhK8KCbeFUUMY4srKKKofp2PbtvwnO/TQbkQdcQjtyDzrXobOAq8/FUWBmBBURaZ6/BLTW4xTqX1DgPCkBmWoAn6OAHgA5lIEINiGThmHDcgbzYffliNwLPwF50Kxje3AmdKS+3gyBGfJuBzwRSgVA0lw1GtoPhYDgGNUj27CSTidPTk+z/TiYTjUYhJhKJeDyeTCSAp87O5zySSqUhrKysTExMulxzJpPp+DjXJZ3OQCoUCgNfXFySyfqmZ2bsdodmdMxsmX779jBnzmQyEF5Fo+APBoNOpxOG/Dc4E4lEctlXrzwez05gx+vxLC0tAY/FYjlzPAHrJGEIjIKQyWZOPiVOTuNHx6fHR8ehwIt4Kg1bZtLpVCqZOesIQjKVTqYySB1DUc9QEOmKWpqCCKBIxfJxqXxkTD8l6+QMlfzELbtRV91aS5VX0/oJTQJ8Ax/fyCc08XAPuxAUnvMZRQTujQo2idmXSvyzvbmxtrr2/vDdgm4kZNYoS69QL13AlOBv4Vh5d0jXSh7eRNPysUyktJpfdoa7dcI8NHPatgAvs7//2m237y7PPdMpfdqhHatBefW7rh++KSWwCys7UBja77cbb94lISXgrOkuqxHklTP5ktEP797FE4kFh11TW7unU+1qRnhYrFWjMtaX8y99eaeCXFwtKMTQCjD0fAwFAWdBFQeF6+hXTcLfTCaTa/6NCbVOy2izKSQBnco1Pry/Mm8hVzEvniuuoJQSRYVYRj6aUoChIqh7nHqaxGgyn54cw8JbgdCQ5onL7Yt6LYHZqYMlezK4/NZlMZJJnfnX6kg8FIFbWEEvJnThmiSItn/sw5s3YIvshfb/+kBmyfWW5SmLL2IzuQdkjsG+HZc5PDn+/pl9eaSP9ICZD18XQ6ULjVr3G2T92zz390V7Dg/4+1WPYZlo7LV59WjeZJrs4ZoH+4POJ0e+uWxkxftIV1TFQuHacY2icde+fvkU8X39i/+Ly86LeQfRA5pAg6rs4IrVfJX7qftpbHby0GvPRlez4ecnfveCcQxN5N66S2nuHNV7/9YtfkR853/1XfjNd+7HR+yB283SYgL3JpZ9Hd3+RKfPRp592vKGZ43WHo6hhRT3u+TSwZ8xzBaRacQaGp4O5cz+81dXvrpCLyejiIKy+/w7REEBnkOjdysFIjuP72W2z7cxD53T2cjz9elH1CKiSjSldR1ovR//BT7/f6IY6o+gAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5862c629900cfc8f24e8a8527098812b/507e6/2025-07-14-cover.webp 200w,
/static/5862c629900cfc8f24e8a8527098812b/28a80/2025-07-14-cover.webp 400w,
/static/5862c629900cfc8f24e8a8527098812b/64c5a/2025-07-14-cover.webp 429w&quot;
              sizes=&quot;(max-width: 429px) 100vw, 429px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5862c629900cfc8f24e8a8527098812b/36ca5/2025-07-14-cover.png 200w,
/static/5862c629900cfc8f24e8a8527098812b/a3397/2025-07-14-cover.png 400w,
/static/5862c629900cfc8f24e8a8527098812b/2b666/2025-07-14-cover.png 429w&quot;
            sizes=&quot;(max-width: 429px) 100vw, 429px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5862c629900cfc8f24e8a8527098812b/2b666/2025-07-14-cover.png&quot;
            alt=&quot;Do you pick one, or choose wisely? : r/memes&quot;
            title=&quot;Do you pick one, or choose wisely? : r/memes&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;postgresql-when-correctness-is-king&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#postgresql-when-correctness-is-king&quot; aria-label=&quot;postgresql when correctness is king permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;PostgreSQL: When Correctness Is King&lt;/h2&gt;
&lt;p&gt;Let’s start with PostgreSQL, the database that prioritises correctness above all else. Consider this seemingly bulletproof code that could be generated by the ORM used in application:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; quantity &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; inventory &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; product_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;--- ⌛ Application checks if quantity &gt; 0&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; orders &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; product_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; amount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;599.99&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; inventory &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; quantity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; quantity &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; product_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;COMMIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With one server, this works perfectly. With ten servers hitting the same database? You’ve got a problem.&lt;/p&gt;
&lt;p&gt;The issue is that &lt;code class=&quot;language-text&quot;&gt;BEGIN&lt;/code&gt; doesn’t mean “stop the world while I work.” It means “group these operations together.” Between your SELECT and UPDATE, nine other transactions can read the same inventory count. All ten transactions see “5 units available.” All ten proceed with the sale.&lt;/p&gt;
&lt;h3 id=&quot;the-locking-solution&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-locking-solution&quot; aria-label=&quot;the locking solution permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Locking Solution&lt;/h3&gt;
&lt;p&gt;PostgreSQL offers a solution: &lt;code class=&quot;language-text&quot;&gt;SELECT FOR UPDATE&lt;/code&gt;. This actually locks the row:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; quantity &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; inventory &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; product_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;--- ⛔ Now other transactions must wait&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; orders &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; product_id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; amount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;599.99&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; inventory &lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; quantity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; quantity &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; product_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;COMMIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But what actually happens inside PostgreSQL when you use &lt;code class=&quot;language-text&quot;&gt;FOR UPDATE&lt;/code&gt;? Let’s look at the mechanics.&lt;/p&gt;
&lt;p&gt;When Transaction A executes &lt;code class=&quot;language-text&quot;&gt;SELECT FOR UPDATE&lt;/code&gt;, PostgreSQL:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Acquires a &lt;code class=&quot;language-text&quot;&gt;RowExclusiveLock&lt;/code&gt; on the row with &lt;em&gt;product_id = ‘console-1’&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Records this lock in the lock manager&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Other transactions attempting to read with &lt;code class=&quot;language-text&quot;&gt;FOR UPDATE&lt;/code&gt; or write must wait&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can see this in action:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- ✅ Session 1&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; inventory &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; product_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- ⛔ Session 2 (in another connection)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pid&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; relation::regclass&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; granted
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; pg_locks
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; relation &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;inventory&apos;&lt;/span&gt;::regclass&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Output shows something close to:&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- pid  | relation  | mode              | granted&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 1234 | inventory | RowExclusiveLock  | t&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- 5678 | inventory | RowExclusiveLock  | f  -- waiting!&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The waiting transaction appears in &lt;code class=&quot;language-text&quot;&gt;pg_stat_activity&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pid&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; wait_event_type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; wait_event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; query
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; pg_stat_activity
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; wait_event &lt;span class=&quot;token operator&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Shows: wait_event_type = &apos;Lock&apos;, wait_event = &apos;tuple&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This creates a queue. If 100 concurrent users attempt to buy the same product, 99 wait while one is processed. The 100th user might wait several seconds—an eternity in web applications.&lt;/p&gt;
&lt;p&gt;The performance impact is severe. During high traffic, you might process 50 orders per second instead of 1,000 per second. Every popular product becomes a serialisation point for your entire system.&lt;/p&gt;
&lt;p&gt;For more details on PostgreSQL locking, see the &lt;a href=&quot;https://www.postgresql.org/docs/current/explicit-locking.html&quot;&gt;official documentation on explicit locking&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-sequence-problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-sequence-problem&quot; aria-label=&quot;the sequence problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Sequence Problem&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;As I explored in my article about &lt;a href=&quot;/en/postgres_sequence_issues_with_outbox_pattern/&quot;&gt;PostgreSQL sequences and their quirks&lt;/a&gt;, even the basic building blocks of ordering have surprises.&lt;/strong&gt; PostgreSQL sequences—used for generating IDs—can have gaps even when all transactions succeed. Why? Because sequences grab their values before knowing if the transaction will commit.&lt;/p&gt;
&lt;p&gt;Consider this race:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Transaction A starts at 10:00:00.000, gets sequence value 1&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transaction B starts at 10:00:00.001, gets sequence value 2&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transaction B commits at 10:00:00.100&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transaction A commits at 10:00:00.200&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Your records appear “out of order” relative to their IDs.&lt;/strong&gt; Record 2 exists before record 1 in the commit order. This becomes critical when you’re using sequences for ordering in patterns like the &lt;a href=&quot;/en/outbox_inbox_patterns_and_delivery_guarantees_explained/&quot;&gt;Outbox pattern&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The problem runs deeper. PostgreSQL allocates transaction IDs (XIDs) from a global counter. When combined with &lt;a href=&quot;https://www.postgresql.org/docs/current/mvcc.html&quot;&gt;MVCC (Multi-Version Concurrency Control)&lt;/a&gt;, this creates interesting effects:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Transaction XIDs determine visibility&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; xmin&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; xmax&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; inventory &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; product_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- xmin: transaction that created this row version&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- xmax: transaction that deleted/updated this row version&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A row is visible in your transaction if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Its xmin is committed and &amp;#x3C; your transaction’s XID&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Its xmax is either null or &gt; your transaction’s XID&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why PostgreSQL can show you different data than another concurrent transaction—each sees a different “snapshot” of the database based on transaction ordering. Even PostgreSQL makes trade-offs between strict ordering and performance.&lt;/p&gt;
&lt;h3 id=&quot;the-replication-lag&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-replication-lag&quot; aria-label=&quot;the replication lag permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Replication Lag&lt;/h3&gt;
&lt;p&gt;The problems compound with replication. Your primary database knows the correct order. But replicas apply changes asynchronously. A customer might:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Place an order (writes to the primary instance)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check their order history (reads from replica)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;See nothing—the replica hasn’t caught up&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can force reads from the primary, but now you’ve eliminated the performance benefit of having replicas.&lt;/p&gt;
&lt;h2 id=&quot;mongodb-the-speed-trap&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#mongodb-the-speed-trap&quot; aria-label=&quot;mongodb the speed trap permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MongoDB: The Speed Trap&lt;/h2&gt;
&lt;p&gt;MongoDB takes a different philosophy: speed first, consistency when you ask for it.&lt;/p&gt;
&lt;p&gt;Here’s our inventory check in MongoDB:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; product &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;products&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;product&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orders&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;599.99&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;products&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $inc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This has the same race condition as our PostgreSQL example, but MongoDB makes it worse through its replica set architecture.&lt;/p&gt;
&lt;h3 id=&quot;how-mongodb-replication-actually-works&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-mongodb-replication-actually-works&quot; aria-label=&quot;how mongodb replication actually works permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How MongoDB Replication Actually Works&lt;/h3&gt;
&lt;p&gt;MongoDB uses replica sets where one primary accepts writes and secondaries asynchronously replicate from the primary’s oplog (operations log). As I explained in &lt;a href=&quot;/en/the-write-ahead-log-a-foundation/&quot;&gt;the Write-Ahead Log article&lt;/a&gt;, the oplog is MongoDB’s version of a write-ahead log:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// Actual oplog entry structure
{
  &quot;ts&quot;: Timestamp(1626900000, 1),     // Operation timestamp
  &quot;t&quot;: NumberLong(10),                 // Term (for elections)
  &quot;h&quot;: NumberLong(0),                  // Hash
  &quot;v&quot;: 2,                              // Version
  &quot;op&quot;: &quot;u&quot;,                           // Operation type (update)
  &quot;ns&quot;: &quot;shop.products&quot;,               // Namespace
  &quot;ui&quot;: UUID(&quot;...&quot;),                   // Collection UUID
  &quot;o&quot;: { &quot;$v&quot;: 1, &quot;$set&quot;: { &quot;quantity&quot;: 4 } },  // Operation
  &quot;o2&quot;: { &quot;_id&quot;: &quot;console-1&quot; }         // Query
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By default, MongoDB reads might hit different replica set members. The primary might have a quantity of 5, while the secondary still shows a quantity of 10. Your application checks inventory on the secondary (seeing 10), while another server updates the primary (setting it to 0).&lt;/p&gt;
&lt;p&gt;You can see the replication lag yourself:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;db.adminCommand({ replSetGetStatus: 1 }).members.forEach(member =&gt; {
  console.log(`${member.name}: ${member.optime.ts.getTime()}`);
});
// Primary:    Timestamp(1626900000, 1)
// Secondary1: Timestamp(1626899995, 1)  -- 5 seconds behind!&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For deep details on MongoDB replication mechanics, see the &lt;a href=&quot;https://www.mongodb.com/collateral/mongodb-architecture-guide&quot;&gt;MongoDB Architecture Guide&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;mongodbs-atomic-operations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#mongodbs-atomic-operations&quot; aria-label=&quot;mongodbs atomic operations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MongoDB’s Atomic Operations&lt;/h3&gt;
&lt;p&gt;MongoDB offers &lt;code class=&quot;language-text&quot;&gt;findOneAndUpdate&lt;/code&gt; for atomic operations:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;products&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOneAndUpdate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $gt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $inc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; returnDocument&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;after&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Inventory decreased successfully, proceed with order&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orders&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;599.99&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is atomic for the inventory update. But now you have a new problem: what if the order insert fails? You’ve decremented inventory but have no order record.&lt;/p&gt;
&lt;h3 id=&quot;the-session-solution&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-session-solution&quot; aria-label=&quot;the session solution permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Session Solution&lt;/h3&gt;
&lt;p&gt;MongoDB 4.0 added multi-document transactions:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; product &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;products&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;product&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;products&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $inc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orders&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; userId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;console-1&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;599.99&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You’ve just made MongoDB behave like PostgreSQL, inheriting PostgreSQL’s performance characteristics. MongoDB’s own documentation warns that transactions impact performance—in benchmarks, throughput drops by 50-60% with transactions enabled.&lt;/p&gt;
&lt;p&gt;Why? Because MongoDB must now coordinate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Acquire locks across multiple documents&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure all replica set members can see a consistent snapshot&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maintain undo logs for potential rollback&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Block concurrent operations on the same documents&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.mongodb.com/manual/core/transactions/&quot;&gt;MongoDB transactions documentation&lt;/a&gt; explicitly states: “In most cases, multi-document transaction incurs a greater performance cost over single document writes.”&lt;/p&gt;
&lt;h3 id=&quot;the-revision-pattern&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-revision-pattern&quot; aria-label=&quot;the revision pattern permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Revision Pattern&lt;/h3&gt;
&lt;p&gt;In my article about &lt;a href=&quot;/en/projecting_from_marten_to_elasticsearch/&quot;&gt;handling projections in eventually consistent stores&lt;/a&gt;, I explored another challenge: eventual consistency in reads. When you write to MongoDB and immediately read, you might get stale data or even null results.&lt;/p&gt;
&lt;p&gt;The solution involves tracking revisions and retrying until you see the expected state:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;retryIfNotFound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;find&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; maxRetries&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; delay&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; retryCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;retryCount &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxRetries&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Document not found after max retries&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sleepTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; retryCount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;delay&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sleepTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      retryCount&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Usage with revision tracking:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; productItems&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; revision &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;retryIfNotFound&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  shoppingCarts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      revision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $gte&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; expectedRevision &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; projection&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; productItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; revision&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This pattern, retry with exponential backoff until you see the version you expect—works. Normally, one call should be enough, but sometimes it can take multiple retries to get it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Such an approach improves the issue but adds complexity and may increase latency.&lt;/strong&gt; You’re fighting MongoDB’s natural behaviour instead of working with it. Each retry might take hundreds of milliseconds, turning a simple read into a multi-second operation under load.&lt;/p&gt;
&lt;h2 id=&quot;kafka-distributed-by-design&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#kafka-distributed-by-design&quot; aria-label=&quot;kafka distributed by design permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Kafka: Distributed by Design&lt;/h2&gt;
&lt;p&gt;Kafka takes yet another approach: it embraces distribution but partitions the problem.&lt;/p&gt;
&lt;p&gt;As I explained in &lt;a href=&quot;/en/the-write-ahead-log-a-foundation/&quot;&gt;The Write-Ahead Log article&lt;/a&gt;, Kafka is built entirely around the concept of logs. Topics provide logical grouping, while partitions are the physical append-only logs. Each partition guarantees ordering, but there’s no global order across partitions.&lt;/p&gt;
&lt;p&gt;In Kafka, you might model our inventory system as events. Order service publishes event:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;producer.send({
  topic: &apos;orders&apos;,
  key: productId, // Ensures all events for a product go to same partition
  value: {
    type: &apos;OrderPlaced&apos;,
    userId: 123,
    productId: &apos;console-1&apos;,
    amount: 599.99
  }
});&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The Inventory service consumes and updates&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;message&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;OrderPlaced&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inventory &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getInventory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inventory &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;decrementInventory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;publishInventoryReserved&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;publishOrderRejected&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By using the product ID as the partition key, Kafka guarantees all events for a product are processed in order. But this only works within a single partition.&lt;/p&gt;
&lt;h3 id=&quot;the-distributed-transaction-problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-distributed-transaction-problem&quot; aria-label=&quot;the distributed transaction problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Distributed Transaction Problem&lt;/h3&gt;
&lt;p&gt;What happens when a customer orders two different products in one checkout?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Order contains: Product A and Product B&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Event 1: “Reserve inventory for A” → partition 1 (based on hash of “product-A”)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Event 2: “Reserve inventory for B” → partition 7 (based on hash of “product-B”)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Event 3: “Process payment for order” → partition 3 (based on hash of “order-123”)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These events may be processed in any order across partitions. To understand why, look at how Kafka consumer groups work:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// Consumer group with 10 instances, 10 partitions
// Each consumer gets 1 partition
Consumer 1: Reading partition 0 at offset 1000
Consumer 2: Reading partition 1 at offset 5000
Consumer 3: Reading partition 2 at offset 2000
// ... and so on&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each consumer processes its partition independently at its own pace. Partition 1 might be at offset 5000 while partition 7 is still at offset 100. Your payment might process before inventory is even checked.&lt;/p&gt;
&lt;p&gt;Kafka provides ordering within partitions through its ISR (In-Sync Replica) mechanism:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;# Check partition details
kafka-topics --describe --topic orders --bootstrap-server localhost:9092

# Output shows:
# Partition: 0  Leader: 1  Replicas: 1,2,3  Isr: 1,2,3
# Partition: 1  Leader: 2  Replicas: 2,3,1  Isr: 2,3,1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The ISR ensures all in-sync replicas have the same messages in the same order. But this is per partition—there’s no coordination between partitions.&lt;/p&gt;
&lt;p&gt;For more details, see &lt;a href=&quot;https://kafka.apache.org/documentation/#design&quot;&gt;Kafka’s design documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Plus even if you have events on the same partition, then between publishing message and handling it there is always a latency. So data may not be immediately in the end database, and cause discrepancies. Kafka doesn’t support optimistic concurrency.&lt;/p&gt;
&lt;p&gt;That’s why Kafka should not be treated as a database (nor as an event store). See more:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/20zvAJAhqS0?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-saga-pattern&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-saga-pattern&quot; aria-label=&quot;the saga pattern permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Saga Pattern&lt;/h3&gt;
&lt;p&gt;When developers reach these ordering limits, they often turn to the Saga pattern. As I explained in &lt;a href=&quot;/en/saga_process_manager_distributed_transactions/&quot;&gt;Saga and Process Manager - distributed processes in practice&lt;/a&gt;, Saga doesn’t solve the ordering problem—it accepts that you can’t have distributed transactions and builds compensations around that fact.&lt;/p&gt;
&lt;p&gt;The typical solution models the process as a series of compensating transactions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Reserve Product A (can be undone by releasing the reservation)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reserve Product B (can be undone by releasing the reservation)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Process Payment (can be undone by refund)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confirm Reservations (convert to permanent inventory decrease)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But this creates new problems. You’re now managing distributed state machines across multiple services. Each service maintains its own state, for instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;InventoryService: &lt;em&gt;{ “product-A”: “reserved”, “product-B”: “available” }&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PaymentService: &lt;em&gt;{ “order-123”: “pending” }&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;OrderService: &lt;em&gt;{ “order-123”: “awaiting-payment” }&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;States can become inconsistent between services. What if payment succeeds but the inventory times out? What if a network partition happens during compensation?&lt;/p&gt;
&lt;p&gt;Your simple inventory check has become a complex choreography of events, timeouts, and compensations. You haven’t eliminated the distributed transaction problem - you’ve just made it explicit in your application code. Which doesn’t have to be that bad, but introducing it in a simple cases can add latency. Each step requires network calls, persistence, and coordination. You’ve traded database locks for distributed complexity.&lt;/p&gt;
&lt;h3 id=&quot;kafkas-performance-trade-offs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#kafkas-performance-trade-offs&quot; aria-label=&quot;kafkas performance trade offs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Kafka’s Performance Trade-offs&lt;/h3&gt;
&lt;p&gt;Even within single-partition ordering, Kafka makes trade-offs:&lt;/p&gt;
&lt;p&gt;Without a built-in idempotence on producer, producing is faster, but can create duplicates on retry&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; producer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Kafka&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Producer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;client.id&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;inventory-service&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.broker.list&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;localhost:9092&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;With an idempotent producer,&lt;/strong&gt; we won’t have duplicates, but it’ll be slower&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 20% performance hit&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; producer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Kafka&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Producer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;client.id&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;inventory-service&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.broker.list&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;localhost:9092&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;enable.idempotence&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Prevents duplicates&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;max.in.flight.requests.per.connection&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Must be ≤ 5&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And it will be even slower with Kafka “transactions”:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; producer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Kafka&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Producer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;client.id&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;inventory-service&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.broker.list&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;localhost:9092&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;transactional.id&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;inventory-tx-1&apos;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Enables transactions&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; producer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;initTransactions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; producer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;beginTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; producer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;produce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; topic&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;orders&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; producer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;commitTransaction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Why such performance penalties? With transactions, Kafka must:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Coordinate with a transaction coordinator&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write markers to the log&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure all partitions involved commit atomically&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maintain transaction state across failures&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And remember—this ordering only works within partitions. For global ordering, you’d need a single partition, which means single-threaded, sequential processing, one message after another. Your distributed system just became a very expensive single-threaded application. At that point, you might as well use PostgreSQL.&lt;/p&gt;
&lt;h2 id=&quot;the-truth-we-try-to-hide-from-ourselves&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-truth-we-try-to-hide-from-ourselves&quot; aria-label=&quot;the truth we try to hide from ourselves permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The truth we try to hide from ourselves&lt;/h2&gt;
&lt;p&gt;After exploring these three systems, a pattern emerges. Each makes different trade-offs, but they’re all making trade-offs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; chose correctness. You pay with lock contention and reduced throughput.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MongoDB&lt;/strong&gt; choose speed. You pay with application complexity and eventual consistency.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kafka&lt;/strong&gt; chose scalability. You pay with architectural complexity and partial ordering.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There’s no magic configuration or hidden feature that gives you everything. The trade-offs are fundamental.&lt;/p&gt;
&lt;h3 id=&quot;why-is-ordering-so-expensive&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-is-ordering-so-expensive&quot; aria-label=&quot;why is ordering so expensive permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Is Ordering So Expensive?&lt;/h3&gt;
&lt;p&gt;In a single-threaded application, ordering is free. Operations happen one after another. Period.&lt;/p&gt;
&lt;p&gt;In a distributed system, establishing “after” requires coordination. Consider what happens at the network level:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Server A must send a message: “I did X” (minimum 1ms in the same datacenter)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Server B must acknowledge: “I heard you” (another 1ms)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Only then can Server B safely do Y “after” X&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;But it’s worse with multiple participants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;3 servers need 3 message exchanges (A→B, B→C, C→A)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;10 servers need 45 message exchanges (n*(n-1)/2)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;100 servers need 4,950 message exchanges&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each message exchange is at least:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;~0.5ms in the same datacenter&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;~50ms across data centres in the same region&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;~150ms across continents&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For comparison:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Reading from CPU cache: 0.0000001ms&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reading from RAM: 0.0001ms&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reading from SSD: 0.01ms&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Network coordination is 5,000 to 1,500,000 times slower than local operations. This is physics, not poor implementation.&lt;/p&gt;
&lt;h3 id=&quot;the-two-phase-commit-trap&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-two-phase-commit-trap&quot; aria-label=&quot;the two phase commit trap permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Two-Phase Commit Trap&lt;/h3&gt;
&lt;p&gt;You might think: “Use two-phase commit! Coordinate across databases!”&lt;/p&gt;
&lt;p&gt;Two-phase commit (2PC) does guarantee consistency across systems. Here’s the protocol:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1 - Voting&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Coordinator → Participant A: &quot;PREPARE transaction-123&quot;
Coordinator → Participant B: &quot;PREPARE transaction-123&quot;
Participant A → Coordinator: &quot;READY&quot; (locks resources)
Participant B → Coordinator: &quot;READY&quot; (locks resources)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Phase 2 - Completion&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Coordinator → Participant A: &quot;COMMIT transaction-123&quot;
Coordinator → Participant B: &quot;COMMIT transaction-123&quot;
Participant A → Coordinator: &quot;DONE&quot; (releases locks)
Participant B → Coordinator: &quot;DONE&quot; (releases locks)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The killer problem: those locks are held during network round-trip times. With just 2 participants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;2 round trips minimum&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At 1ms per message: 4ms holding locks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;At 50ms per message: 200ms holding locks&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But the real problem is failure handling. If the coordinator crashes after Phase 1:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Participants have locked resources&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They don’t know whether to commit or abort&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They must keep the locks until the coordinator recovers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;This could be forever&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is called the “blocking problem” of 2PC. Your entire system stops because one component fails. Modern systems avoid 2PC for this reason. Even with perfect networks, the performance cost is prohibitive.&lt;/p&gt;
&lt;p&gt;Google Spanner uses atomic clocks and GPS synchronisation to reduce coordination needs, and it still has uncertainty windows of 7ms. Amazon DynamoDB uses vector clocks and still requires application-level conflict resolution. If companies with unlimited resources can’t eliminate these trade-offs, the trade-offs are fundamental to distributed systems.&lt;/p&gt;
&lt;h2 id=&quot;making-peace-with-physics&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#making-peace-with-physics&quot; aria-label=&quot;making peace with physics permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Making Peace with Physics&lt;/h2&gt;
&lt;p&gt;So what do you do? You can’t beat physics, but you can work with it.&lt;/p&gt;
&lt;h3 id=&quot;pattern-1-reduce-coordination-scope&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pattern-1-reduce-coordination-scope&quot; aria-label=&quot;pattern 1 reduce coordination scope permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pattern 1: Reduce Coordination Scope&lt;/h3&gt;
&lt;p&gt;Instead of coordinating everything, coordinate only what matters. In our inventory example, don’t coordinate this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;await checkUserAuthentication();
await validateShippingAddress();
await calculateTaxes();&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But focus on the critical business check:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;await atomicallyDecrementInventory();&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Most operations don’t need global ordering. Identify the few that do and design around them.&lt;/p&gt;
&lt;h3 id=&quot;pattern-2-business-level-solutions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pattern-2-business-level-solutions&quot; aria-label=&quot;pattern 2 business level solutions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pattern 2: Business-Level Solutions&lt;/h3&gt;
&lt;p&gt;Sometimes the best solution isn’t technical. Amazon shows “Only 3 left in stock!” not just for marketing—it’s also setting expectations. If they occasionally oversell by one unit, customers understand.&lt;/p&gt;
&lt;p&gt;Other business solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Time-bound reservations: “Item held for 10 minutes”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inventory buffers: Keep 5% hidden reserve&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Graceful degradation: “High demand! Order confirmed pending inventory check”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;automatic ordering of items if the stock was zeroed, and asking the user whether they want to wait or get their money back.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;pattern-3-choose-your-guarantees&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pattern-3-choose-your-guarantees&quot; aria-label=&quot;pattern 3 choose your guarantees permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pattern 3: Choose Your Guarantees&lt;/h3&gt;
&lt;p&gt;Not all operations need the same guarantees:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Financial transactions&lt;/strong&gt;: Use a database with proper locking (e.g. PostgreSQL). Speed matters less than correctness.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Product browsing&lt;/strong&gt;: You can use a database with eventual consistency (MongoDB, ElasticSearch). Speed and feature set may matter more than perfect accuracy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Analytics events&lt;/strong&gt;: Use Kafka with at-least-once delivery. Order within user sessions is enough.&lt;/p&gt;
&lt;h3 id=&quot;pattern-4-embrace-idempotency&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pattern-4-embrace-idempotency&quot; aria-label=&quot;pattern 4 embrace idempotency permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pattern 4: Embrace Idempotency&lt;/h3&gt;
&lt;p&gt;Make operations safe to retry:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;// 👎 Bad: Changes meaning if applied twice
UPDATE inventory SET quantity = quantity - 1;

// 👍 Good: Same result if applied multiple times
UPDATE inventory SET quantity = 5 WHERE version = 42;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With idempotent operations, you can retry without fear, compensate for reordering, and recover from partial failures.&lt;/p&gt;
&lt;h2 id=&quot;conclusion-pick-your-poison&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion-pick-your-poison&quot; aria-label=&quot;conclusion pick your poison permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion: Pick Your Poison&lt;/h2&gt;
&lt;p&gt;Every distributed system makes trade-offs between ordering and performance. There’s no configuration flag or architectural pattern that eliminates this trade-off—only different ways to manage it.&lt;/p&gt;
&lt;p&gt;PostgreSQL gives you correctness but makes you pay in lock contention. MongoDB gives you speed but makes you handle consistency yourself. Kafka gives you scale but requires complex coordination patterns.&lt;/p&gt;
&lt;p&gt;The key is understanding which trade-off you’re making and designing accordingly. Don’t fight your database’s natural behaviour—work with it.&lt;/p&gt;
&lt;p&gt;Next time you see eventual consistent reads in MongoDB, you’ll understand why. When PostgreSQL seems slow under load, you’ll know what you’re paying for. When Kafka requires complex event choreography, you’ll see it as the price of distribution.&lt;/p&gt;
&lt;p&gt;The speed of light isn’t just a suggestion. Network calls aren’t free. Coordination has a cost. Once you accept these constraints, you can stop looking for silver bullets and start making informed trade-offs.&lt;/p&gt;
&lt;p&gt;The question isn’t “How do I get both?” The question is “What can I afford to give up?”&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Dealing with Eventual Consistency, and Causal Consistency using Predictable Identifiers]]></title><description><![CDATA[Users want to attach files and keep working. Systems need time to upload, store those files and link it to the specific process or entity…]]></description><link>https://event-driven.io/en/dealing-with-eventual-consistency/</link><guid isPermaLink="false">https://event-driven.io/en/dealing-with-eventual-consistency/</guid><content:encoded>&lt;p&gt;Users want to attach files and keep working. Systems need time to upload, store those files and link it to the specific process or entity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You have two choices: block the interface during uploads, or accept temporary inconsistency.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I learned this while building a construction documentation system. Users attached blueprints to tasks, photos to safety reports, and contracts to milestones. Construction files can be pretty big, so we didn’t want users to have to wait for the upload and can’t do any tasks during that. We needed something better.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/en/predictable-identifiers-enabling/&quot;&gt;The previous article showed how predictable ids can help in having module autonomy.&lt;/a&gt; We discussed &lt;a href=&quot;https://de.wikipedia.org/wiki/Uniform_Resource_Name&quot;&gt;URNs (Unique Resource Names)&lt;/a&gt; and took the payments module as an example. We used them to generate upfront correlation between other modules and payments. Thanks to that, we inverted the dependency. The payment module could expose a generic API without being aware of the other modules’ existence. Payments just had to know about the correlation id generated upfront and forward it through its own operations.&lt;/p&gt;
&lt;p&gt;Document management faces similar coupling challenges, as well as concerns regarding network reliability and user experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let’s follow up and discuss today how predictable ids can also help in solving eventual consistency challenges.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;eventual-consistency-vs-causal-consistency&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#eventual-consistency-vs-causal-consistency&quot; aria-label=&quot;eventual consistency vs causal consistency permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Eventual Consistency vs Causal Consistency&lt;/h2&gt;
&lt;p&gt;Most of the time, we discuss strong consistency and eventual consistency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most systems prefer to have strong consistency. You perform the operation, wait till it’s finished and then proceed.&lt;/strong&gt; We could handle uploads synchronously. User selects file → browser uploads to server → server stores file → server creates database record → server returns success. Simple, consistent, slow.&lt;/p&gt;
&lt;p&gt;This creates problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Large files block the UI for minutes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Network interruptions force complete restarts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Servers become bottlenecks, streaming every byte through memory&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Multi-gigabyte files can crash servers&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Coupling user experience with implementation details can cause problems. Users don’t care about moving bytes across networks. They selected the file - to them, it’s attached.&lt;/p&gt;
&lt;p&gt;Luckily, we learned that sometimes we have to live with an additional delay. Still, we should not stop that, as it’s a bit trickier than “a small delay”.&lt;/p&gt;
&lt;p&gt;We usually assume that stuff will happen in a particular order, we just don’t know precisely when. And that’s actually something that’s called Causal Consistency. The difference between Eventual and Causal consistency is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eventual consistency&lt;/strong&gt; - The system eventually reaches a consistent state. Order of operations doesn’t matter. Create a link, then upload the file, or upload the file, then create the link - the result is the same. The system tolerates temporary inconsistency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Causal consistency&lt;/strong&gt; - Operations must respect cause and effect. You can’t comment on a document before it exists. Effects follow causes.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 640px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/65d790c74e66aef9ac786e753d086df4/797b9/2025-07-07-cover.webp&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 96.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/webp;base64,UklGRswAAABXRUJQVlA4IMAAAADQBACdASoUABMAPtFgqU+oJaOiKAgBABoJQBllsY2GxQXMCb3CVTZT6c+Se3Kv7AD+2BbGI8HGhrRrYYx7njyC5RAbU6J/hIvJu8tcRrmWJCTT12gJCKrQvryuw/v5+DIZINyZyj8efLV2UQIXbBQ9VcljFHazl7DExfyDqhLi9iyCQxEe/f+wb+hOARyCZseG1yulwOcSzuMdAChoNtR2zO3SQOxcNErp28YXkXseXRfXZ99Wehg45Ln/ieusUAA=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/65d790c74e66aef9ac786e753d086df4/507e6/2025-07-07-cover.webp 200w,
/static/65d790c74e66aef9ac786e753d086df4/28a80/2025-07-07-cover.webp 400w,
/static/65d790c74e66aef9ac786e753d086df4/797b9/2025-07-07-cover.webp 640w&quot;
              sizes=&quot;(max-width: 640px) 100vw, 640px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/65d790c74e66aef9ac786e753d086df4/507e6/2025-07-07-cover.webp 200w,
/static/65d790c74e66aef9ac786e753d086df4/28a80/2025-07-07-cover.webp 400w,
/static/65d790c74e66aef9ac786e753d086df4/797b9/2025-07-07-cover.webp 640w&quot;
            sizes=&quot;(max-width: 640px) 100vw, 640px&quot;
            type=&quot;image/webp&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/65d790c74e66aef9ac786e753d086df4/797b9/2025-07-07-cover.webp&quot;
            alt=&quot;2025 07 07 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://x.com/gregyoung/status/1101642600342265857&quot;&gt;https://x.com/gregyoung/status/1101642600342265857&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;File uploads fit eventual consistency perfectly. When attaching a blueprint to a construction task, two things happen: storing the file and linking it to the task. The order is irrelevant. What matters is that both are completed eventually.&lt;/p&gt;
&lt;p&gt;With eventual consistency for file uploads, we can show files as “attached” immediately. The actual upload happens in the background. Of course, we won’t be able to download them until the upload is finished, but the system continues working during the brief inconsistency.&lt;/p&gt;
&lt;h2 id=&quot;the-urn-solution-embracing-eventual-consistency&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-urn-solution-embracing-eventual-consistency&quot; aria-label=&quot;the urn solution embracing eventual consistency permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The URN Solution: Embracing Eventual Consistency&lt;/h2&gt;
&lt;p&gt;In our system, we decided to separate cloud storage from the actual application behaviour. Our files module was a generic one, similar to the discussed payments.&lt;/p&gt;
&lt;p&gt;The reasoning, as in construction projects, is that you may have multiple places where someone can upload documentation, scans, and photos. We wouldn’t like to couple everything together. We just let specific modules to take care of linking files to their processes.&lt;/p&gt;
&lt;p&gt;What’s more, we didn’t even want to have a direct relationship on the backend between files and the business module. File URNs were generated on the UI before uploading. The UI knows the context in which the file will reside before transferring bytes.&lt;/p&gt;
&lt;p&gt;When users select a file, we could generate a URN immediately:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;urn:files:1:CONSTRUCTION:SAFETY_CHECK:DAILY_INSPECTION:site-photo.jpg&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Structure breakdown:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;files&lt;/code&gt; - namespace for documents&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;1&lt;/code&gt; - version for format evolution&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;CONSTRUCTION&lt;/code&gt; - initiating module&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;SAFETY_CHECK&lt;/code&gt; - business process&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;DAILY_INSPECTION&lt;/code&gt; - subprocess&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;site-photo.jpg&lt;/code&gt; - filename&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The URN structure also helps detect duplicates. The user won’t be able to upload the same file twice, as the key is unique.&lt;/p&gt;
&lt;p&gt;Ok, but how will we know where to upload the file when we just know some magic URN?&lt;/p&gt;
&lt;h2 id=&quot;pre-signed-urls-direct-client-to-storage-uploads&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pre-signed-urls-direct-client-to-storage-uploads&quot; aria-label=&quot;pre signed urls direct client to storage uploads permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pre-Signed URLs: Direct Client-to-Storage Uploads&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Modern cloud storage providers offer pre-signed URLs, which are time-limited URLs that allow direct uploads without requiring permanent credentials.&lt;/strong&gt; Your server generates these URLs on demand, then hands them to the client. The client uploads directly to S3, Azure Blob Storage, or similar services.&lt;/p&gt;
&lt;p&gt;But why are pre-signed URLs better than traditional browser uploads? With traditional uploads, your browser establishes a single HTTP connection to your server. If that connection drops - and on mobile networks, it will - the entire upload fails. The browser can’t resume; it must start from scratch. Your server, meanwhile, has been holding that partial upload in memory, consuming resources for nothing. With Cloud, that can mean huge costs, as you’re paying for each byte transferred outside your network.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pre-signed URLs shift this burden to battle-tested cloud infrastructure.&lt;/strong&gt; &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html&quot;&gt;S3 presigned URLs&lt;/a&gt; allow anyone with valid security credentials to create a URL that grants temporary access to upload an object. The URL is limited by the permissions of the user who created it. They provide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Automatic retry logic&lt;/strong&gt;: The storage provider retries failed chunks without restarting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resumable uploads&lt;/strong&gt;: Large files can be uploaded using the multipart upload API.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Global edge locations&lt;/strong&gt;: Files are uploaded to the nearest datacenter, reducing latency.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bandwidth optimisation&lt;/strong&gt;: Cloud providers have better peering agreements than your servers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Are they secure? That’s, of course, depends on your requirements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Following the &lt;a href=&quot;https://martinfowler.com/articles/microservices.html#SmartEndpointsAndDumbPipes&quot;&gt;dumb pipes, smart endpoints principle&lt;/a&gt;, we’re treating the resigned URL as a dumb pipe.&lt;/strong&gt; Our endpoint should be smart enough to ensure that we authenticate and authorise users to perform an operation.&lt;/p&gt;
&lt;p&gt;For S3, generating a pre-signed URL requires only the target key and expiration. AWS documentation notes that: &lt;em&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html&quot;&gt;“When you create a presigned URL, you must provide your security credentials and specify a bucket name, an object key, an HTTP method (PUT for uploading objects), and an expiration date and time.”&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The presigned URLs are valid only for the specified duration:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generatePresignedUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; command &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PutObjectCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    Bucket&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;bucket&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    Key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; key
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// URL expires in 1 hour&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getSignedUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;s3Client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; expiresIn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3600&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/api/resources/onedrive&quot;&gt;Microsoft Graph API&lt;/a&gt; offers similar capabilities through upload sessions. According to Microsoft’s documentation: &lt;em&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0&quot;&gt;“Create an upload session to allow your app to upload files up to the maximum file size. An upload session allows your app to upload ranges of the file in sequential API requests. Upload sessions also allow the transfer to resume if a connection is dropped while the upload is in progress.”&lt;/a&gt;&lt;/em&gt; The implementation follows a similar pattern:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createUploadSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;UploadSession&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;graphClient
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/drive/root:/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;path&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:/createUploadSession&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      item&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token string-property property&quot;&gt;&quot;@microsoft.graph.conflictBehavior&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;rename&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    uploadUrl&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uploadUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    expirationDateTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expirationDateTime
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This pattern removes re-streaming through your servers. We also benefit from a built-in storage provider’s replication. A user in Singapore uploading a 100MB video sends data to the nearest AWS edge location, not to your servers in Europe. Your server handles business logic instead of moving bytes around.&lt;/p&gt;
&lt;p&gt;But now you can’t track upload progress. So again, how do you know when the file arrives?&lt;/p&gt;
&lt;h2 id=&quot;the-magic-folder-pattern&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-magic-folder-pattern&quot; aria-label=&quot;the magic folder pattern permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The “Magic Folder” Pattern&lt;/h2&gt;
&lt;p&gt;We took inspiration from Microsoft Graph API “Special Folders”. It provides a sneaky pattern for addressing files by path. Per docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Special folders provide simple aliases to access well-known folders in OneDrive without the need to look up the folder by path (which would require localization), or reference the folder with an ID. If a special folder is renamed or moved to another location within the drive, this syntax will continue to find that folder.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What are those special folders? Known from Windows folders like &lt;em&gt;Documents&lt;/em&gt;, &lt;em&gt;Recordings&lt;/em&gt;, &lt;em&gt;Photos&lt;/em&gt; etc.&lt;/p&gt;
&lt;p&gt;Thanks to understanding the URN structure, we could design specific modules with separate paths to drives or buckets, simply mapping them logically in the backend to specific buckets. This could be either full conventional-based, or stored in a static config or database.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;No need to pre-create folder hierarchies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Files automatically organise by business context.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Migration preserves logical structure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When combined with our URN approach, this creates a powerful system. A URN like &lt;code class=&quot;language-text&quot;&gt;urn:files:1:CONSTRUCTION:SAFETY_CHECK:DAILY_INSPECTION:site-photo.jpg&lt;/code&gt; maps directly to a storage path: &lt;code class=&quot;language-text&quot;&gt;files/v1/construction/safety_check/daily_inspection/site-photo.jpg&lt;/code&gt;. The storage provider creates any missing folders automatically, just like Microsoft’s “magic folder” behavior.&lt;/p&gt;
&lt;p&gt;In S3, this works because S3 doesn’t actually have folders - it uses key prefixes that look like paths. When you upload to &lt;code class=&quot;language-text&quot;&gt;files/v1/construction/safety_check/daily_inspection/site-photo.jpg&lt;/code&gt; S3 treats the forward slashes as part of the object key. The AWS console displays these as folders for convenience, but they’re just a naming convention. This flat structure with hierarchical naming gives us the benefits of organisation without the overhead of managing actual folders.&lt;/p&gt;
&lt;p&gt;Instead of explicit registration:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Generate URN&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Upload to the corresponding path&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The system finds a file through URN pattern matching&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Modules query for files matching their patterns:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TaskModule&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTaskWithAttachments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;taskId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;TaskWithFiles&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; task &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;taskId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Find all document links for this task&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pattern &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;urn:files:*:TASKS:*:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;taskId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:*&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; links &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findDocumentLinks&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pattern&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Return task with attachment metadata&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;task&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      attachments&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; links&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;link &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extractFileName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        uploadedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;uploadedAt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        downloadUrl&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;uploaded&apos;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateDownloadUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The task module doesn’t need to know about the upload process. It queries for documents matching its pattern. Whether those documents are still uploading, have been completed, or have failed, the module displays the appropriate status to users.&lt;/p&gt;
&lt;p&gt;The “magic” works through convention. Upload the file to the correct path, and it will become visible to the relevant modules.&lt;/p&gt;
&lt;h2 id=&quot;composition-through-ui-and-process-based-apis&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#composition-through-ui-and-process-based-apis&quot; aria-label=&quot;composition through ui and process based apis permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Composition Through UI and Process-Based APIs&lt;/h2&gt;
&lt;p&gt;These patterns work well together. The UI generates URNs based on user context. Process-based APIs accept these URNs without knowing their origin. Storage services handle files based on URN patterns alone.&lt;/p&gt;
&lt;p&gt;But what is task-based UI? Traditional CRUD interfaces are organised around data models: “Create Document”, “Edit Document”, and “Delete Document”. Task-based UIs organise around user goals: “Submit Safety Report”, “Approve Change Order”, “Complete Inspection”. Each interface shows only the actions relevant to that specific task.&lt;/p&gt;
&lt;p&gt;This design provides the context needed for URN generation. When a safety inspector uploads a photo, the UI knows they’re performing a safety inspection. It knows which site, which inspection type, and which date.&lt;/p&gt;
&lt;p&gt;The flow works like this. We need a function to generate a derived URN. It can be either as dumb as this, or more likely a bit smarter:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Client-side URN generation&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateFileUrn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    module&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    process&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    subprocess&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    fileName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; safeName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fileName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;[^a-zA-Z0-9.-]&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;_&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;urn:files:1:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;process&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;subprocess&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;safeName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nevertheless, how creative we are in the mapping then we could use it as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createRemediationTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;violationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; contractorId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; File&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; urn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;generateFileUrn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;CONSTRUCTION&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;SAFETY_CHECK&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;VIOLATION_${violationId}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Immediately show the file as attached&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;showFileAsAttached&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; file&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Request upload URL from backend&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; uploadUrl &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; api&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestUploadUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Create remediation task with photo reference&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; api&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createRemediationTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    violationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    assignedTo&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; contractorId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    attachments&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;urn&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    dueDate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;calculateDueDate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;severity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Upload in background&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;uploadInBackground&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;file&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; uploadUrl&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The backend tracks these uploads using link metadata:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DocumentService&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestUploadUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; uploadUrl&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Generate pre-signed URL using URN as the storage key&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; storageKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;urnToStorageKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// verify persmissions&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hasPermissions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;storageKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; uploadUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generatePresignedUrl&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;storageKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; uploadUrl &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each component has one job. The UI handles user interaction and URN generation. The API manages business logic without dealing with file storage. The storage service handles bytes and pre-signed URLs. Clean separation of concerns through predictable identifiers.&lt;/p&gt;
&lt;h2 id=&quot;cleanup-and-validation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cleanup-and-validation&quot; aria-label=&quot;cleanup and validation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cleanup and Validation&lt;/h2&gt;
&lt;p&gt;Accepting eventual consistency means accepting temporary inconsistency. But “temporary” needs boundaries. Sometimes just accepting uploads as they go is not enough. We may need an audit around it, and simply using path traversal as we did is not sufficient for you. Or you’d like to see also pending uploads, or perform an evaluation of file content before showing it to users (e.g., an anti-virus scan).&lt;/p&gt;
&lt;p&gt;We could also keep an explicit track of document links together with metadata, store them in the database when we’re initiating, and upload metadata.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DocumentLink&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  urn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  uploadedAt&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  fileSize&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  contentType&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;pending&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;uploaded&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;failed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  expiresAt&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We could either subscribe to the storage notifications (if they give us such) or just run a background service that validates uploads and cleans up failures:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;class DocumentValidationService {
  async validatePendingUploads&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;: Promise&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;void&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; {
    &lt;span class=&quot;token comment&quot;&gt;// Find uploads pending for more than 15 minutes&lt;/span&gt;
    const staleLinks &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; await this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token identifier&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;
      SELECT * FROM document_links
      WHERE status = &apos;pending&apos;
      AND created_at &amp;lt; NOW() - INTERVAL &apos;15 minutes&apos;
    &lt;span class=&quot;token punctuation&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;const link &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; staleLinks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; {
      const storageKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urnToStorageKey&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urn&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      const &lt;span class=&quot;token keyword&quot;&gt;exists&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; await this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;checkExists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;storageKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; {
        &lt;span class=&quot;token comment&quot;&gt;// File uploaded successfully, update metadata&lt;/span&gt;
        const metadata &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; await this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;storage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;getMetadata&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;storageKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        await this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;document_links&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; {
          urn: link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;: &lt;span class=&quot;token string&quot;&gt;&apos;uploaded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          uploadedAt: metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastModified&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          fileSize: metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          contentType: metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;contentType
        }&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      } &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expiresAt &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; new &lt;span class=&quot;token keyword&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; {
        &lt;span class=&quot;token comment&quot;&gt;// Upload expired, mark as failed&lt;/span&gt;
        await this&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;document_links&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; {
          urn: link&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;urn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;status&lt;/span&gt;: &lt;span class=&quot;token string&quot;&gt;&apos;failed&apos;&lt;/span&gt;
        }&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The periodic job serves three critical functions:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Upload Status Reconciliation&lt;/strong&gt;: Files might upload successfully but the client fails to notify the server. Network issues, browser crashes, or users closing tabs can interrupt the completion callback. The reconciliation job discovers these “orphaned successes” and updates their status.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Dead Link Detection&lt;/strong&gt;: Users sometimes delete files directly from cloud storage. External tools, manual cleanup, or storage lifecycle policies can remove files the system expects to exist. The job detects these deletions and marks links accordingly, preventing 404 errors when users try to download.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Database Hygiene&lt;/strong&gt;: Failed uploads and deleted file records accumulate over time. The job purges old records (typically after 30 days) to prevent unbounded table growth. This matters at scale - millions of uploads can generate significant metadata that serves no purpose after a reasonable retention period.&lt;/p&gt;
&lt;p&gt;The timing of these jobs requires careful consideration. Too frequently, and you waste resources checking files that rarely change. Too infrequently, and users encounter stale data. Most systems find 5-15 minute intervals work well. Critical systems might run more frequently with smart batching to limit resource usage.&lt;/p&gt;
&lt;p&gt;We could also trigger a follow-up operation in through such job, notifying the outside world about the upload failure or completion.&lt;/p&gt;
&lt;h2 id=&quot;provider-independence-through-urns&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#provider-independence-through-urns&quot; aria-label=&quot;provider independence through urns permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Provider Independence Through URNs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Last but not least: URNs hide storage details.&lt;/strong&gt; The URN &lt;code class=&quot;language-text&quot;&gt;urn:files:1:CONSTRUCTION:SAFETY_CHECK:DAILY_INSPECTION:site-photo.jpg&lt;/code&gt; works whether files live in S3, Azure, or on-premises storage. For a testing environment, we could use a regular disk location.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Migration becomes straightforward.&lt;/strong&gt; We just need to move files and provide the mapping function. Once the migration is made in the files module, we can just resolve them to a different place. The change will be opaque for other modules. The URN abstraction separates the system from storage details.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The final flow looks as follows:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f3b7c21af01ada364eb66fabbc948adc/2959b/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 87.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAACXBIWXMAAAsTAAALEwEAmpwYAAACXUlEQVR42o2UCZOiMBCF/f//b2tXAZVzgHCjeJDk7UuDs7s1TLlWtYbQ/fn6SHZ487H2gnmO0PcdSlVwHXPPl/3V4x//3XvgCGNCAksMQ8lnB/T+D2jtNnCeU5Rljo+PDFqn3D18Au0a9IrdLQ+a3w8quTFgkjXwFAetRzyfJ1yvMS2RtTEHWs+3M2PvEudirH0uQGMqjGONNM0RhjHquuNzzOCJNgpAa2ce1wEV+7jdjmhbhaKoEEUJKsbc78UC1LpijVrESUlgijx3L3NRacyVv4GAtPa5PlKJW0esaccyVIzLoKqBMcoBrUgFXOBttYl7Exb1lw3gUYDAmWpDZnEmJeL+ebvLf/fmO6AxZ4JyNqunwoIZutJE2Fk70KGXIk+TEtO6X8dl5LoWyLbCE/QcEBzIWhS6IQV8GYVhiOF7Bfb7CL6fs+gDA0NxNsb/UkOnyvcLHA4xG9OxUZUDniTAOboZu1xKgmI6Z3QoViVUqF/ABf54RBwjhaahf1NwKhoqVQ54lIDFnHSP3VrScso/gWvKi9JfbMawzq9rqF5n0nwFPp8J4vjOf25X0JlKOqTJAM9LmVrLLBq42m8evRfwpcCdhLoemUK+dvdMxSlUGSPLfCiVsBTxGyCLfJv2a40C1sfZ63RkDK643zDN5ddad0k8Ni+T3R91r9YH6Lof7PhPAvZSaGOsjNA4KjnbS822P7vrteJxaxEcldx585zx6JUcWCUjM88l1bYY+hBRmLF+CcP6zXQF+Hi4Ka/Y+pK1UXJVXS6FjIQDGtNS9Y1KUw59TXj67V3ogL8BFsVyVK9vArgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/f3b7c21af01ada364eb66fabbc948adc/507e6/image-2.webp 200w,
/static/f3b7c21af01ada364eb66fabbc948adc/28a80/image-2.webp 400w,
/static/f3b7c21af01ada364eb66fabbc948adc/8d2ea/image-2.webp 800w,
/static/f3b7c21af01ada364eb66fabbc948adc/296ed/image-2.webp 1139w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/f3b7c21af01ada364eb66fabbc948adc/36ca5/image-2.png 200w,
/static/f3b7c21af01ada364eb66fabbc948adc/a3397/image-2.png 400w,
/static/f3b7c21af01ada364eb66fabbc948adc/a331c/image-2.png 800w,
/static/f3b7c21af01ada364eb66fabbc948adc/2959b/image-2.png 1139w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/f3b7c21af01ada364eb66fabbc948adc/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Choosing eventual consistency for file uploads improves user experience. Users attach files and continue working while uploads happen in the background. Perfect consistency would force them to wait.&lt;/p&gt;
&lt;p&gt;Pre-signed URLs and predictable URNs make this possible. Files are uploaded directly to storage while the system tracks progress through link metadata. URNs provide routing and correlation without coupling. The system stays functional during the brief inconsistency window.&lt;/p&gt;
&lt;p&gt;As we saw with the payment module in the previous article, predictable identifiers change how modules communicate. For file uploads, they also change how we handle consistency. Instead of forcing synchronous behaviour on an asynchronous process, we work with the distributed nature of the system. The architecture scales with load rather than against it.&lt;/p&gt;
&lt;p&gt;Working with system constraints beats fighting them. Eventual consistency is an example of this.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Predictable Identifiers: Enabling True Module Autonomy in Distributed Systems]]></title><description><![CDATA[In distributed systems, we face a fundamental tension between module communication and module autonomy. Our modules need to exchange…]]></description><link>https://event-driven.io/en/predictable-identifiers-enabling/</link><guid isPermaLink="false">https://event-driven.io/en/predictable-identifiers-enabling/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;In distributed systems, we face a fundamental tension between module communication and module autonomy.&lt;/strong&gt; Our modules need to exchange information and coordinate actions, yet we want each module to evolve independently without forcing changes across the entire system. This tension becomes evident in event-driven architectures, where modules communicate through events and messages.&lt;/p&gt;
&lt;p&gt;Consider a typical e-commerce platform. The payment module needs to process requests from orders, handle reimbursements, manage subscriptions, and potentially deal with dozens of other payment scenarios. Each connection point between modules represents a potential coupling that can ripple through the system when requirements change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The traditional approach involves modules directly referencing each other through event types, API endpoints, or shared data structures.&lt;/strong&gt; This creates a web of dependencies where adding a new module or changing an existing one requires coordinated updates across multiple teams.&lt;/p&gt;
&lt;p&gt;What if we could enable modules to communicate without knowing about each other’s existence? What if a payment module could process requests from any source without being programmed to handle specific scenarios?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We’ll explore today how predictable identifiers, specifically Uniform Resource Names (URNs), provide an elegant solution to this challenge.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By structuring identifiers in a predictable format, we enable infrastructure-level routing and correlation without module-specific logic. This approach transforms tightly coupled systems into collections of autonomous services that communicate through well-defined patterns rather than explicit dependencies.&lt;/p&gt;
&lt;h2 id=&quot;why-generic-modules-matter&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-generic-modules-matter&quot; aria-label=&quot;why generic modules matter permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Generic Modules Matter&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Payment processing in modern applications rarely involves direct credit card handling.&lt;/strong&gt; Instead, we integrate with payment service providers like Stripe, PayPal, Square, etc. Each provider offers different APIs, webhook patterns, and state machines. A payment module serves as an abstraction layer, wrapping these external services to present a unified interface to the rest of our system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consider what a typical payment module handles. It translates internal payment requests into provider-specific API calls.&lt;/strong&gt; It manages authentication tokens and API keys for multiple providers. It handles webhook signatures and payload verification. It normalises responses across different providers, mapping various status codes and error messages into a consistent format. It implements retry logic and circuit breakers for provider outages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This wrapper pattern makes sense from a separation of concerns perspective.&lt;/strong&gt; Business modules shouldn’t need to understand Stripe’s payment intent flow or PayPal’s authorisation and capture model. They should simply request a payment and receive a result.&lt;/p&gt;
&lt;p&gt;However, making this wrapper truly generic presents challenges. Different parts of our system need payments for different reasons. An order needs payment before fulfilment. A subscription needs recurring payments. A marketplace needs to split payments between vendors. A reimbursement needs to refund previous payments. Each scenario has unique requirements, yet they all need the same underlying capability: moving money.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The key insight is that payment processing, at its core, involves the same operations regardless of the business context.&lt;/strong&gt; Whether we’re charging for an order or processing a reimbursement, we’re essentially creating a financial transaction with an amount, currency, and customer. The challenge lies in building a payment module that can serve all these scenarios without becoming coupled to any of them.&lt;/p&gt;
&lt;p&gt;Modern payment providers like Stripe have recognised this pattern. Their Payment Intents API offers a standardised approach to handling various payment scenarios.&lt;/p&gt;
&lt;h2 id=&quot;the-coupling-problem---payment-module-example&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-coupling-problem---payment-module-example&quot; aria-label=&quot;the coupling problem   payment module example permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Coupling Problem - Payment Module Example&lt;/h2&gt;
&lt;p&gt;Let’s examine how coupling emerges in a traditional event-driven architecture. Imagine our payment module listening for events from various parts of the system:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OrderCreated&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;OrderCreated&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  customerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReimbursementRequested&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ReimbursementRequested&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  reimbursementId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  originalPaymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  customerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SubscriptionRenewalDue&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;SubscriptionRenewalDue&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  subscriptionId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  customerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  planId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  nextBillingDate&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MarketplacePurchaseEvent&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;MarketplacePurchase&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  purchaseId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  buyerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  splits&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    vendorId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    commission&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The pseudocode for handling that could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentModule&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; eventBus&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventBus&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; paymentProvider&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentProvider
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// The payment module must know about every event type&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventBus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;OrderCreated&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleOrderCreated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventBus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ReimbursementRequested&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleReimbursementRequested&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventBus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;SubscriptionRenewalDue&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleSubscriptionRenewal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventBus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;MarketplacePurchase&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleMarketplacePurchase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleOrderCreated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; OrderCreatedEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createPayment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currency&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      customerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customerId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orderId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Must publish order-specific response event&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventBus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;OrderPaymentCompleted&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleReimbursementRequested&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ReimbursementRequestedEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Different logic for reimbursements&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createPayment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;originalPaymentId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reason&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; reimbursementId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reimbursementId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Different response event type&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;eventBus&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ReimbursementProcessed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      reimbursementId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;reimbursementId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      refundId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleMarketplacePurchase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MarketplacePurchaseEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Yet another response event type&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// More handlers for each business scenario...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This design creates multiple problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First, the payment module must know about every business module that needs payments.&lt;/strong&gt; When a new feature requires payment processing, we must modify the payment module to handle its specific event type and business rules.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second, each integration requires custom logic.&lt;/strong&gt; Orders might need immediate capture, subscriptions need scheduled billing, and marketplaces need split payments. The payment module becomes a collection of business-specific handlers rather than a generic payment service.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Third, response handling creates reverse dependencies.&lt;/strong&gt; The payment module must know how to notify each requesting module, publishing module-specific completion events. This doubles the coupling surface area.&lt;/p&gt;
&lt;p&gt;The release train problem becomes apparent when we trace a simple change. Suppose the order module needs to add a discount field. This requires updating the OrderCreatedEvent structure, modifying the payment module’s handler to process discounts, changing the response event structure, and coordinating deployment of both modules. What should be a simple change to order processing ripples through the entire system.&lt;/p&gt;
&lt;p&gt;Testing becomes increasingly complex as the payment module accumulates dependencies. Unit tests must mock events from every integrated module. Integration tests require coordinating multiple modules. Adding a new payment scenario means updating test suites across multiple codebases.&lt;/p&gt;
&lt;p&gt;The payment module also loses its generic nature. Instead of being a thin wrapper over external payment providers, it becomes an orchestrator of business logic. It knows that orders need immediate payment, reimbursements relate to previous transactions, and subscriptions recur monthly. This business logic properly belongs in the respective business modules, not in the payment infrastructure.&lt;/p&gt;
&lt;h2 id=&quot;partial-solution-command-pattern&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#partial-solution-command-pattern&quot; aria-label=&quot;partial solution command pattern permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Partial Solution: Command Pattern&lt;/h2&gt;
&lt;p&gt;One approach to reducing coupling involves exposing a command interface instead of listening for specific events. Rather than the payment module knowing about orders, reimbursements, and subscriptions, other modules send generic payment requests:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestPayment&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  customerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  description&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  metadata&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Record&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And handle it like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentModule&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Generic payment processing - knows nothing about business context&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestPayment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestPayment&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PaymentResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Just wraps the payment provider&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;processPayment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currency&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      customerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customerId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currency
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This inverts the dependency. Instead of the payment module depending on other modules’ event types, other modules depend on the payment module’s command interface. The payment module no longer needs to be aware of orders or reimbursements; it simply processes payment requests.&lt;/p&gt;
&lt;p&gt;Now, business modules can use this generic interface:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OrderModule&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;processOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Order&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; paymentResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestPayment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currency&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      customerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customerId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Order &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;paymentResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;succeeded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;markOrderAsPaid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; paymentResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;However, this solution introduces new challenges. In distributed systems, payment processing often happens asynchronously.&lt;/strong&gt; Stripe might take several seconds to process a payment. 3D Secure authentication requires user interaction. Webhooks arrive minutes or hours after the initial request. The requesting module needs to know when the payment completes, but how does the payment module notify the requester without creating a reverse dependency?&lt;/p&gt;
&lt;p&gt;We could use callbacks or return addresses, but this reintroduces coupling in a different form. The payment module would need to understand how to communicate with each requesting module, whether through specific message queues, HTTP endpoints, or event types.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We’ve simply moved the coupling problem rather than solving it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What we need is a way for modules to correlate requests and responses without explicit knowledge of each other. The requesting module should be able to say “process this payment” and later receive notification of completion without the payment module knowing anything about the requester. This requires a more sophisticated approach to identification and routing.&lt;/p&gt;
&lt;h2 id=&quot;complete-solution-urn-based-predictable-identifiers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#complete-solution-urn-based-predictable-identifiers&quot; aria-label=&quot;complete solution urn based predictable identifiers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Complete Solution: URN-Based Predictable Identifiers&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Uniform_Resource_Name&quot;&gt;Uniform Resource Names (URNs)&lt;/a&gt; provide a standardised way to create globally unique, persistent identifiers. Unlike URLs, which specify location, URNs specify identity. By structuring these identifiers predictably, we can encode routing and correlation information directly into the identifier itself.&lt;/p&gt;
&lt;p&gt;Consider this URN format for our payment system:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;urn:payments:{version}:{source}:{tenantid}:{uniqueid}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s break down each component. The &lt;code class=&quot;language-text&quot;&gt;payments&lt;/code&gt; namespace identifies this as a payment-related identifier. The &lt;code class=&quot;language-text&quot;&gt;source&lt;/code&gt; indicates which module or system initiated the payment request. The &lt;code class=&quot;language-text&quot;&gt;tenantid&lt;/code&gt; enables multi-tenant routing at the infrastructure level. The &lt;code class=&quot;language-text&quot;&gt;uniqueid&lt;/code&gt; ensures global uniqueness, often incorporating timestamps or UUIDs. The &lt;code class=&quot;language-text&quot;&gt;version&lt;/code&gt; allows for format evolution while maintaining backward compatibility.&lt;/p&gt;
&lt;p&gt;Here are concrete examples:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;urn:payments:1:ORD:TN1:20240115-0jdfj93
urn:payments:1:RMB:TN1:20240115-ksd8234
urn:payments:1:SUB:TN2:20240115-mnb9821
urn:payments:1:MKT:TN1:20240115-poi8734
urn:payments:1:EXT:TN1:CRM1-jdj292&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first identifier represents a payment initiated by the order module (ORD) for tenant TN1. The second comes from the reimbursement module (RMB). The third originates from subscriptions (SUB) for a different tenant. The fourth is from the marketplace module (MKT). The fifth shows an external system (EXT) integration where CRM1 might be an external CRM’s identifier.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;URNs offer several advantages over ad-hoc identifier schemes.&lt;/strong&gt; They follow &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8141.html&quot;&gt;RFC 8141&lt;/a&gt;, providing a well-documented standard. They’re hierarchical, allowing for logical organisation and pattern matching. They’re opaque to external systems while being meaningful to internal infrastructure. Most importantly, they’re self-describing, carrying their own routing information.&lt;/p&gt;
&lt;p&gt;The implementation requires careful consideration of field naming and placement. We could call this field &lt;code class=&quot;language-text&quot;&gt;processId&lt;/code&gt;, emphasizing its role in process correlation, or &lt;code class=&quot;language-text&quot;&gt;correlationId&lt;/code&gt;, highlighting its use in linking related operations. Some teams prefer &lt;code class=&quot;language-text&quot;&gt;contextId&lt;/code&gt; to indicate that it carries contextual information beyond simple identification.&lt;/p&gt;
&lt;p&gt;Let’s update our payment request to use URNs:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentRequest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  correlationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// The URN&lt;/span&gt;
  amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  customerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  description&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  metadata&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Record&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the implementation to:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentModule&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestPayment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Process payment asynchronously&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;payment.requests&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;processPaymentAsync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;processPayment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currency&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        customerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customerId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          correlationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;correlationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Publish response using URN for routing&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publishResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;correlationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publishResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;correlationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;failed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;publishResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;correlationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; response&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;any&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Infrastructure handles routing based on URN&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;payment.responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      correlationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;response
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;implementation-rabbitmq-routing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#implementation-rabbitmq-routing&quot; aria-label=&quot;implementation rabbitmq routing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Implementation: RabbitMQ Routing&lt;/h2&gt;
&lt;p&gt;Let’s implement this pattern using RabbitMQ to demonstrate infrastructure-level routing. &lt;a href=&quot;https://www.cloudamqp.com/blog/part4-rabbitmq-for-beginners-exchanges-routing-keys-bindings.html&quot;&gt;RabbitMQ implement various strategies to route messages between queues, they do it through Exchanges&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exchanges are routing layers&lt;/strong&gt; that receive messages and route them to queues based on specific rules. They don’t store messages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Queues are storage layers -&lt;/strong&gt; they buffer messages until consumers process them. Messages physically reside here.&lt;/p&gt;
&lt;p&gt;Think of it as a postal system: exchanges are sorting facilities, queues are mailboxes. Publishers are unaware of mailboxes (queues); they simply send messages to the sorting facility (exchange) with an address (routing key).&lt;/p&gt;
&lt;p&gt;We’ll use topic exchanges. They enable routing based on routing keys with wildcards, perfect for our URN structure.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/62e0eb078ed5b9c1c9dcc5e236ecfc3f/image-2.svg&quot; alt=&quot;rabbitmq topic exchange&quot; title=&quot;rabbitmq topic exchange&quot;&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://www.cloudamqp.com/blog/part4-rabbitmq-for-beginners-exchanges-routing-keys-bindings.html&quot;&gt;https://www.cloudamqp.com/blog/part4-rabbitmq-for-beginners-exchanges-routing-keys-bindings.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;First, we’ll set up the infrastructure configuration:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentsMessagingConfiguration&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setupRouting&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Channel&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Main exchange for payment responses&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assertExchange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;topic&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; durable&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Each module creates its own response queue&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assertQueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;orders-payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; durable&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assertQueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;reimbursements-payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; durable&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;assertQueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;subscriptions-payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; durable&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Bind queues with routing patterns based on URN components&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Orders module receives responses for payments it initiated&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindQueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;orders-payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;payments.*.ORD.*.*&apos;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Reimbursements module receives its responses&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindQueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;reimbursements-payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;payments.*.RMB.*.*&apos;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Tenant-specific routing for multi-tenant systems&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bindQueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;tenant-tn1-payments&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;payments.*.*.TN1.*&apos;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now let’s implement the complete flow from order creation through payment completion:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OrderModule&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;payOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; order &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Generate URN for this payment&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; correlationId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;urn:payments:1:ORD:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tenantId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Request payment with URN&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentsClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;requestPayment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      correlationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;total&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      currency&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currency&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      customerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customerId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Order &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handlePaymentResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentCompleted &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PaymentFailed
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; correlationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; paymentId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Extract order ID from URN&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; urnParts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; correlationId&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; orderId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; urnParts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Update order based on payment status&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;succeeded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;markOrderAsPaid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; paymentId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;failed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;markOrderAsPaymentFailed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;orderId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The payment module processes requests and publishes responses without knowing the source:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentModule&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onPaymentCompletedProviderWebhook&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PaymentGatewayPaymentCompleted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;    
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;      
      &lt;span class=&quot;token comment&quot;&gt;// Extract routing key from URN&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// urn:payments:1:ORD:TN1:123 becomes payments.1.ORD.TN1.123&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// to match RabbitMQ naming convention&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; routingKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;correlationId
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;urn:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Publish response using URN-based routing&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; routingKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        correlationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;correlationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; routingKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;correlationId
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;urn:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;channel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;payment-responses&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; routingKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        correlationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;correlationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;failed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here’s the complete flow visualised:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/67b302f7f73034d56a3a0879637d1bd2/13d2c/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABkklEQVR42o1Sy27bMBDM//9PEaCXArkkv9DAucSGHSmmKEp8vyZD2hHSBm2ywJISl5yd2d0bNKtAmBP8HFEzNvMl42RWjM7go2U7w8sjsmPMJxyMg86lx268D7A8mE4Kr/sJVjuEEHow1oJRrxDOojJp835uFxhxQokGIiQcVwP7DlhK4cWKGCOEmLAy2NwYi8Bk3voe/2jtTQjx03cH3GQw0Jg619x3QGsdwTWMtZgmyYSS+8yztd/5P2DOZJnwt7UEy6IgpwnDywvGYcAkRD+rtb3x8N5/B/AiM7snpPUO2TxgHn5hv7vF8+MPnA8/kfU9orpDNL+3N/8ELKUipUwmCxY1sxwaWi/QlLso1VkG38pjydB9zbDVJqUEKRVlCqi51Y1N8pG1pcwvmxIzWXgCl86sFb01ZHwVOJ4GDMOZoI3tynpKjpfemuk+AbJcerSQB4XocgfMdEUpT+cRe0puTN5HzM0j1HGHxAE/mICdXKCu6jbAmrjkrRcXOfyJXEMpf7a+JN7nfHIPLQE9XWf1DXvHWnHqX/AYAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/67b302f7f73034d56a3a0879637d1bd2/507e6/image-3.webp 200w,
/static/67b302f7f73034d56a3a0879637d1bd2/28a80/image-3.webp 400w,
/static/67b302f7f73034d56a3a0879637d1bd2/8d2ea/image-3.webp 800w,
/static/67b302f7f73034d56a3a0879637d1bd2/7118e/image-3.webp 1128w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/67b302f7f73034d56a3a0879637d1bd2/36ca5/image-3.png 200w,
/static/67b302f7f73034d56a3a0879637d1bd2/a3397/image-3.png 400w,
/static/67b302f7f73034d56a3a0879637d1bd2/a331c/image-3.png 800w,
/static/67b302f7f73034d56a3a0879637d1bd2/13d2c/image-3.png 1128w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/67b302f7f73034d56a3a0879637d1bd2/a331c/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;additional-benefits&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#additional-benefits&quot; aria-label=&quot;additional benefits permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Additional Benefits&lt;/h2&gt;
&lt;p&gt;The predictable identifier pattern yields benefits beyond basic routing and correlation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Webhook correlation becomes straightforward when external systems call back.&lt;/strong&gt; Mature payment providers allow us to pass the external id or send custom metadata, including our URN. When a webhook arrives, we extract the correlation ID and route it back to the originating module without any hardcoded logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Natural idempotency emerges from URNs.&lt;/strong&gt; The correlation ID itself can serve as an idempotency key, preventing duplicate payment processing when modules retry failed requests. This is especially valuable for payment operations where accidental duplicates can be costly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Infrastructure-level tenant routing becomes possible.&lt;/strong&gt; Since the tenant ID is embedded in the URN structure, the routing infrastructure can direct messages to tenant-specific queues or processing clusters without the payment module being aware of multi-tenancy concerns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Debugging and observability improve dramatically.&lt;/strong&gt; Every operation carries its correlation ID through the entire flow, making it simple to trace a payment from initiation through completion. Log aggregation tools can group all events by correlation ID, providing a complete view of each transaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most importantly, the payment module remains completely generic.&lt;/strong&gt; It processes payments without knowing whether they originate from orders, subscriptions, reimbursements, or any future module. This is the essence of an open host service: a truly autonomous module that provides its capability without coupling to its consumers.&lt;/p&gt;
&lt;h2 id=&quot;security-and-practical-considerations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#security-and-practical-considerations&quot; aria-label=&quot;security and practical considerations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Security and Practical Considerations&lt;/h2&gt;
&lt;p&gt;While predictable identifiers offer numerous benefits, they also introduce security considerations. The structure itself reveals information about your system’s organisation, potentially exposing module names and tenant identifiers to external observers.&lt;/p&gt;
&lt;p&gt;Protect against identifier traversal attacks logically can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SecurePaymentHandler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handlePaymentResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    message&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Message&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RequestContext
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; correlationId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Validate tenant access&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; urnTenant &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extractTenant&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;correlationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urnTenant &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tenantId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ForbiddenError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Access denied&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Validation based on source&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extractSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;correlationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;allowedSources&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ForbiddenError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Source not authorized&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Additionally, you can store and verify correlation exists&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; exists &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;correlationStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exists&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;correlationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;exists&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotFoundError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Invalid correlation&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Process valid response&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;processResponse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rate limiting can also leverage the URN structure:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;UrnBasedRateLimiter&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkLimit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;correlationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extractSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;correlationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tenant &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;extractTenant&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;correlationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Different limits for different sources&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; limits &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token constant&quot;&gt;ORD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Orders can make many payments&lt;/span&gt;
      &lt;span class=&quot;token constant&quot;&gt;RMB&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;// Reimbursements are less frequent&lt;/span&gt;
      &lt;span class=&quot;token constant&quot;&gt;EXT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;     &lt;span class=&quot;token comment&quot;&gt;// External systems get lower limits&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; limit &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; limits&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;ratelimit:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;tenant&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;source&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;redis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;incr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;redis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;expire&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3600&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1 hour window&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; count &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; limit&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Consider encryption for sensitive components (or best avoid sending such):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SecureUrnGenerator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;generateUrn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;source&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tenantId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; uniqueId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Version 2 URNs use encryption&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Encrypt sensitive parts&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; encryptedTenant &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encrypt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tenantId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; encryptedId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encrypt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;uniqueId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Base64URL encode for safety&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; safeTenant &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;encryptedTenant&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;base64url&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; safeId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;encryptedId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;base64url&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;urn:payments:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;version&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;source&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;safeTenant&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;safeId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;parseSecureUrn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;urn&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ParsedUrn &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; urn&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; version &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;version &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Unsupported URN version&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      source&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      tenantId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decrypt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;base64url&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      uniqueId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decrypt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;base64url&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can, of course, go further, support checksums, etc.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Predictable identifiers transform how modules communicate in distributed systems. By encoding routing and correlation information directly into identifiers, we enable true module autonomy.&lt;/strong&gt; The payment module example demonstrates this transformation clearly - from a tightly coupled system where the payment module must know about every business scenario, to a generic service that processes payments without knowing their source.&lt;/p&gt;
&lt;p&gt;The key insight is that identity can carry meaning without creating coupling. URNs provide a standardised way to structure this identity, enabling infrastructure-level routing while maintaining module independence. The payment module processes generic payment requests and publishes responses using the correlation ID for routing. It never needs to know whether a payment is for an order, reimbursement, or subscription.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This pattern extends beyond payments to any cross-module communication.&lt;/strong&gt; Any module that provides generic services - notifications, document generation, audit logging - benefits from predictable identifiers. The pattern is particularly powerful in event-driven architectures where asynchronous communication and webhook handling are common.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implementation requires thoughtful design choices around format, security, and infrastructure integration.&lt;/strong&gt; However, the investment pays dividends in reduced coupling, improved debugging, and easier system evolution. Teams can work independently, modules can be added or removed without system-wide changes, and the promise of microservice architecture finally becomes reality.&lt;/p&gt;
&lt;p&gt;As systems grow in complexity, the need for autonomous modules becomes critical. Predictable identifiers offer a proven pattern for achieving this autonomy without sacrificing coordination. They represent a fundamental shift in how we think about module communication - from explicit contracts to emergent behaviour, from tight coupling to flexible collaboration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Are you already using URN-based strategies in your systems?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;Queuing, Backpressure, Single Writer and other useful patterns for managing concurrency&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/idempotent_command_handling/&quot;&gt;Idempotent Command Handling&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/ordering-grouping-and-consistency/&quot;&gt;Ordering, Grouping and Consistency in Messaging systems&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Start Alone, Then Together: Why Software Modelling Needs Solitary Brainstorming]]></title><description><![CDATA[Watch a jazz quartet improvise, and you might think they’re making it up as they go. They’re not. Every riff builds on practised scales and…]]></description><link>https://event-driven.io/en/start-alone-then-together-why-software/</link><guid isPermaLink="false">https://event-driven.io/en/start-alone-then-together-why-software/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Watch a jazz quartet improvise, and you might think they’re making it up as they go. They’re not.&lt;/strong&gt; Every riff builds on practised scales and learned patterns. The constraints don’t stifle creativity—they make it work.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/edbe1146e297b0cca8bd356337f7cc4d/7d442/2025-06-23-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC4ElEQVR42h2S228iZRjGJ1ETtVCgHIfDDANMh/NAgbIttKVLOVPAFLqtrUjbpa5FTfaQ1d24MaYxGq1G18QLN656Z9T/8efnXjw3T97v93zvQTIjYWprWc6HLcatBud3RhwN9zke9GlkYqzLNqphD5WYQn+zzLPRBkfrWXp5k6uyTlJ2YbdY8SzbcAtJOQGsZFPMBk3mp4ecjQWs22TS71PPJoU35njYZCcWZHtzi+uTPebVNO21HNO8hrpiw7q0hMNiYcW6jGRGIpQTBu8KSKOySaO0zn5nwLi/T6u4xnw25ctvvqJf3aBZqbLobTMp56ilktQifgJONyGnB9XrJ+QJIBWNVbKaRjmb4/7l+9y7OKfb6NCrt/n91594/NGc41aNTkG02N2hZmg8mnS4XlxQMU02U3EKukHUpxCRw0gZASuk0gzqu/z97x/89c9LzESGYr7C2eEBp8Mul+M263qEpKqxY0Q5q2/x5y/fcnV+wp16gWomQ0z8LiarSKrHx8nkgBe//cjN8xuePn1COBxHCem0d+uMWntsmGmSUQ27zYEZjtG93eDTqyntSolEWGGnUCQu6ldDMSTF42XQ6/PwwX1GvQGNVhslqJFNFjHTBVRFtOL3YQS8LC07iKg6mXSWVqNGQizUvmynGE+/gunBKFLY5yPo85MUphaKYrW6yCRL5NJl/C4Zj8OF7PbiF3LYXBirGd58y8LS21bsdqeot2GEdRKajiHCJE2WkZ0rvPH6awS9QWRPkETqFsVyk6BIdLs8lG9t0dmrEv1/efkcQX8ARdEIBELY7CIwoKKHghhCkt8lDJcTm9WCTzz2uv0oapxIxEQJJ4hGkrwzHPLz909Yy5pMj0ZM3zvk4SczHi1OqZZLfPf1Y26uF3y2OETyijuSXW4xC+uroQdkjZCaFrA8ulEitlqivtfjw3tzPhYnVK93OJgcc/dixuKDGbvbt7m8e8YXzx7w/IfP+Q9yDWtxOMDOpAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/edbe1146e297b0cca8bd356337f7cc4d/507e6/2025-06-23-cover.webp 200w,
/static/edbe1146e297b0cca8bd356337f7cc4d/28a80/2025-06-23-cover.webp 400w,
/static/edbe1146e297b0cca8bd356337f7cc4d/8d2ea/2025-06-23-cover.webp 800w,
/static/edbe1146e297b0cca8bd356337f7cc4d/68fc1/2025-06-23-cover.webp 1200w,
/static/edbe1146e297b0cca8bd356337f7cc4d/43d96/2025-06-23-cover.webp 1600w,
/static/edbe1146e297b0cca8bd356337f7cc4d/f3ff0/2025-06-23-cover.webp 1920w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/edbe1146e297b0cca8bd356337f7cc4d/36ca5/2025-06-23-cover.png 200w,
/static/edbe1146e297b0cca8bd356337f7cc4d/a3397/2025-06-23-cover.png 400w,
/static/edbe1146e297b0cca8bd356337f7cc4d/a331c/2025-06-23-cover.png 800w,
/static/edbe1146e297b0cca8bd356337f7cc4d/8537d/2025-06-23-cover.png 1200w,
/static/edbe1146e297b0cca8bd356337f7cc4d/1a152/2025-06-23-cover.png 1600w,
/static/edbe1146e297b0cca8bd356337f7cc4d/7d442/2025-06-23-cover.png 1920w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/edbe1146e297b0cca8bd356337f7cc4d/a331c/2025-06-23-cover.png&quot;
            alt=&quot;Tips For Learning Jazz Improvisation&quot;
            title=&quot;Tips For Learning Jazz Improvisation&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Image source: &lt;a href=&quot;https://nolaschoolofmusic.com/blog/tips-for-learning-jazz-improvisation&quot;&gt;https://nolaschoolofmusic.com/blog/tips-for-learning-jazz-improvisation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Most software teams get this when writing code. You wouldn’t build a critical system without established patterns, architectural principles, or coding standards. But put those same developers in a modelling session, and they drop all structure. The CTO speaks first, and everyone else responds. The loudest developer dominates the whiteboard. The most persuasive architect steers every decision.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m calling that&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;“the CTO effect”&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The problem isn’t, of course, malicious. Quieter voices simply don’t get heard when the first person to speak sets the direction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The solution: start modelling sessions with 10-15 minutes of solitary brainstorming before anyone speaks.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This breaks the hierarchy cycle at its source.&lt;/strong&gt; When everyone generates ideas simultaneously in silence, there’s less chance for the first voice to set the agenda. No senior person’s opening statement to shape everyone else’s thinking. The junior developer and the CTO start from the same blank point for those crucial first minutes.&lt;/p&gt;
&lt;h2 id=&quot;why-written-ideas-persist&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-written-ideas-persist&quot; aria-label=&quot;why written ideas persist permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Written Ideas Persist&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Written ideas are harder to dismiss than spoken thoughts.&lt;/strong&gt; When someone voices an idea in a meeting, it exists for thirty seconds before the next person talks over it. Write that same idea on a shared board, and it stays visible. It’s much harder to ignore something sitting there in front of everyone.&lt;/p&gt;
&lt;p&gt;Once you’ve written something on a sticky note, you materialise it, and it exists. It’s mentally harder to handwave it. That requires justification. The same idea spoken aloud dies with a simple &lt;em&gt;“hmm, interesting”&lt;/em&gt; before moving on.&lt;/p&gt;
&lt;p&gt;It’s not a novel idea, of course, other companies are already doing it. AWS start meetings with a silent reading time. Everyone quietly reviews the same document before discussion begins. No talking, no interrupting, no immediate reactions. Ideas get absorbed without social dynamics taking over. By the time the discussion starts, everyone works from the same information baseline rather than whoever spoke first setting the agenda. Read more &lt;a href=&quot;https://justingarrison.com/blog/2021-03-15-the-document-culture-of-amazon/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;when-hierarchy-shapes-thinking&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#when-hierarchy-shapes-thinking&quot; aria-label=&quot;when hierarchy shapes thinking permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;When Hierarchy Shapes Thinking&lt;/h2&gt;
&lt;p&gt;This happens beyond just CTOs. Senior architects get deference. The developer who wrote the original system carries weight. The expensive consultant commands attention.&lt;/p&gt;
&lt;p&gt;These people can be seen as those who know what they’re talking about. And quite often they do. But not always. Sometimes they just extrapolate their past experience, and that’s dangerous, as Mathias Verraes wrote in &lt;em&gt;The “It’s Just Like…” Heuristic:&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Your brain rewards you for classifying new information into existing buckets1. Looking for similarities costs less energy than understanding new differences. When you say “Oh I get it, it’s just like …”, then it probably is not “just like”. That’s very counterintuitive, because that “oh I get it” feeling is intuition, not reason2. You feel good about the insight, so it’s very tricky to then go “Oh I get it, so I must be wrong”.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, they can be more often right than you, but then if they fail, that can be quite spectacular.&lt;/p&gt;
&lt;p&gt;In other words, their presence narrows the solution space before alternatives get explored. You may be optimising for consensus, not quality. You build the system that the most senior person envisioned, not necessarily the system that best solves the problem.&lt;/p&gt;
&lt;p&gt;Solitary brainstorming counters this. When everyone generates ideas simultaneously and silently, the junior developer’s insight about edge cases appears alongside the architect’s system design with equal visual weight.&lt;/p&gt;
&lt;h2 id=&quot;structure-helps-improvisation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#structure-helps-improvisation&quot; aria-label=&quot;structure helps improvisation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Structure Helps Improvisation&lt;/h2&gt;
&lt;p&gt;Going back to our improvisation metaphor.&lt;/p&gt;
&lt;p&gt;Jazz musicians work within scales, follow chord progressions, and respond to patterns the group establishes. The structure doesn’t limit creativity—it gives creativity a framework to work within.&lt;/p&gt;
&lt;p&gt;Setting a format for your session doesn’t have to limit it; it can help you focus on the merit and reduce the likelihood of wandering off.&lt;/p&gt;
&lt;p&gt;The brainstorming phase outcome provides the raw material, events, questions, and ideas from everyone’s perspective. The EventStorming, C4 and others notations help in not having discussions on how to write stuff. We can cut time for questions on “what does this colour mean?” or “what is this box about?”. The structure helps facilitate discussions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;solitary brainstorming&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;approach requires discipline, which explains why teams skip it.&lt;/strong&gt; Starting with a discussion feels easier and more natural. But if you’re already blocking time for modelling sessions, you’ve committed to doing the work properly.&lt;/p&gt;
&lt;p&gt;So, try it on your own, set a timer for 10-15 minutes. Everyone works silently, generating ideas related to the modelling question. Use a shared digital board (like Miro), where ideas appear in real-time, but refrain from commenting until the time expires. This is active brainstorming, not a time for preparation. Quantity beats quality at this stage.&lt;/p&gt;
&lt;p&gt;After the timer goes off, you have a board full of ideas from everyone, not just whoever speaks first or loudest. The collaborative phase can focus on synthesis and evaluation—what groups do well—instead of generation.&lt;/p&gt;
&lt;p&gt;Individual brainstorming creates raw material. Group collaboration refines it. Ask a group to do both simultaneously, and one suffers.&lt;/p&gt;
&lt;h2 id=&quot;how-eventstorming-works-here&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-eventstorming-works-here&quot; aria-label=&quot;how eventstorming works here permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How EventStorming Works Here&lt;/h2&gt;
&lt;p&gt;EventStorming sessions show this approach in practice. Instead of having the domain expert explain how everything works while everyone takes notes, everyone spends the first phase independently writing domain events on orange sticky notes. Things that happen in the business that stakeholders care about.&lt;/p&gt;
&lt;p&gt;The result can look messy. Some events are duplicates with different wording. Some are too abstract, others too specific. Some are probably wrong. That’s the point.&lt;/p&gt;
&lt;p&gt;You now have raw material representing everyone’s understanding of the domain, not just whoever speaks first. The next phase, grouping similar events, identifying gaps, building a timeline, becomes collaborative work in reconciling different perspectives instead of rubber-stamping one person’s vision.&lt;/p&gt;
&lt;p&gt;Keep every sticky note during initial grouping. Every event that someone thought worth writing about stays visible, even if it ends up in the “questions” or “parking lot” sections. This prevents the premature filtering that occurs when ideas only exist as spoken words.&lt;/p&gt;
&lt;h2 id=&quot;what-ive-observed&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-ive-observed&quot; aria-label=&quot;what ive observed permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What I’ve Observed&lt;/h2&gt;
&lt;p&gt;In my experience, modelling sessions that start this way become less about convincing others of predetermined solutions and more about exploring a shared problem space. When everyone contributes ideas upfront, people seem more willing to engage with the discussion.&lt;/p&gt;
&lt;p&gt;This isn’t about protecting feelings or ensuring everyone gets heard. Software systems are complex enough that no single person’s mental model captures everything. The goal is to develop more effective systems.&lt;/p&gt;
&lt;p&gt;I’ve found that individual work makes collaborative work more productive. Skip the solo brainstorming, and you haven’t saved time: you’ve just moved idea generation into group discussion, where it’s slower and less effective.&lt;/p&gt;
&lt;p&gt;Software modelling is complex enough without predictable social dynamics making it more challenging.&lt;/p&gt;
&lt;p&gt;What’s your take?&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/the-underestimated-power-of-hot-spots/&quot;&gt;The Underestimated Power of Hot Spots and Notes in EventStorming&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/on-getting-the-meaningful-discussions/&quot;&gt;On getting the meaningful discussions, and why that’s important&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/fifteen_tips_on_how_to_run_meetings_effectively/&quot;&gt;15 tips on how to run meetings effectively&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/agile_vs_introverts/&quot;&gt;Agile vs Introverts&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Secondary Indexes and the Specialized Storage Dilemma]]></title><description><![CDATA[In 2016, the Kafka team faced a problem that illustrates a fundamental challenge in distributed systems design. Users kept asking: “How do I…]]></description><link>https://event-driven.io/en/secondary-indexes-and-the-specialized/</link><guid isPermaLink="false">https://event-driven.io/en/secondary-indexes-and-the-specialized/</guid><content:encoded>&lt;p&gt;In 2016, the Kafka team faced a problem that illustrates a fundamental challenge in distributed systems design. Users kept asking:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“How do I query the current state of my data without replaying the entire log?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you’re tracking user balances through a stream of transactions, you shouldn’t need to replay millions of events just to find out someone’s current balance.&lt;/p&gt;
&lt;p&gt;The problem was that Kafka’s storage was optimised for sequential log consumption and was inefficient at point queries.&lt;/p&gt;
&lt;h2 id=&quot;the-cost-of-specialisation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-cost-of-specialisation&quot; aria-label=&quot;the cost of specialisation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Cost of Specialisation&lt;/h2&gt;
&lt;p&gt;Building your own storage engine means making specific trade-offs. Kafka’s log storage uses sequential writes to append-only segment files, enabling high throughput.&lt;/p&gt;
&lt;p&gt;We discussed the details already in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how-a-kafka-like-producer-writes/&quot;&gt;How Kafka Producer Works internally&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/kafka-consumers-under-the-hood-of/&quot;&gt;Kafka Consumers: Under the Hood of Message Processing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/understanding-kafkas-consumer-protocol/&quot;&gt;Understanding Kafka’s Consumer Protocol: A Deep Dive into How Consumers Talk to Brokers&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how-does-kafka-know-what-was-the/&quot;&gt;How does Kafka know what was the last message it processed? Deep dive into Offset Tracking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Consumers read these segments sequentially, often using zero-copy transfers directly from the file system cache to the network.&lt;/strong&gt; But this design makes point queries expensive. Want to find a specific user’s latest state? You’ll need to scan through potentially gigabytes of log data. There’s no index because indexes would require random writes, which would hurt the sequential write performance that makes Kafka useful for streaming.&lt;/p&gt;
&lt;p&gt;This is the paradox of specialised storage: the optimisations that make you successful also constrain your options. Your users chose your system because it excels at one thing, but success brings diverse use cases. They’ve already invested in your system, their data lives there, and they don’t want to set up another piece of infrastructure just to run different queries.&lt;/p&gt;
&lt;h2 id=&quot;the-kafka-state-store-solution&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-kafka-state-store-solution&quot; aria-label=&quot;the kafka state store solution permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Kafka State Store Solution&lt;/h2&gt;
&lt;p&gt;The Kafka team had three options:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add indexes to the log storage (degrading its sequential write performance).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tell users to export data to a database (increasing operational complexity).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build a separate storage tier optimised for point queries.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;They chose option three.&lt;/strong&gt; Around this time, the Kafka team was developing Kafka Streams, a stream processing library. One of its features fit the state query need: embedded state stores.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So the team built &lt;a href=&quot;https://kafka.apache.org/documentation/streams/&quot;&gt;Kafka Streams&lt;/a&gt;, a Java library that processes data from Kafka topics.&lt;/strong&gt; They embedded a completely different database inside these processing applications. That database is &lt;a href=&quot;https://rocksdb.org/&quot;&gt;RocksDB&lt;/a&gt;, an embedded key-value store that &lt;a href=&quot;https://engineering.fb.com/2013/11/21/core-infra/under-the-hood-building-and-open-sourcing-rocksdb/&quot;&gt;Facebook originally built for fast local storage&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unlike Kafka’s append-only logs, RocksDB uses an &lt;a href=&quot;https://en.wikipedia.org/wiki/Log-structured_merge-tree&quot;&gt;LSM-tree structure&lt;/a&gt; that’s optimised for random reads and writes. You can look up any key instantly without scanning through unrelated data. By embedding RocksDB inside each stream processor, they created local “state stores” that could answer point queries.&lt;/p&gt;
&lt;p&gt;This architecture means that your Kafka messages flow through a Java application that maintains a local database of the current state. If you’re tracking user balances, the application reads transaction events from Kafka and updates each user’s balance in its local RocksDB. Now you can instantly query any user’s current balance, without replaying the log.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However, you’ve created a new problem: the RocksDB instance is local to a single machine.&lt;/strong&gt; If the machine fails, you lose the state. So Kafka added yet another component: a changelog topic.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3edb94bf2dd34286db16ae23f7f74f9b/d99bd/2025-06-16-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB1ElEQVR42j1Si26bQBD0/39Ev6IvJa1UKWoTJ3VkKbIj8wguBmyD4YyBe3JM985RkFar0y6zOzM7k1JCa42mYbi9uUWw2cB90zT5bIzFcPwG1UUYR2Doe/QUXdeBc051A03hslIKMwc2TSNY0+Drl88IgoDQ4BtGQtBmhGx+wMgElgZcLi3atgVjDEIIP9Ra63OWZQQoLxCnJ5zzO/TlHFp2qMsz8iz3DcPQ42UX4s9mjsfoAb3qPxgYGuam/ww6fFoIvKwDAlQckz7CygKTKmFH7Sm4H1w4GvVQIat3eCsSHKoDnBrWjhjdZtbgYZnibsmwfn0H1HwP3qbQwx5GXzW9bgE4jU9diTgPEWwDFMfCU5wm62U5Momian1/GIaOcgtezVGnv3xWokVNDUVRoCxLCCmwzle4efqOx3gO1p3RVIxqR3AhER8E/m5q7BqQ/gSolKRp+hpWeypaG++gJDC3rSYZuOQQShADQ2ZI77J5l8aZ97Gh5GdvRrv/je5wD0HvS9uTGYMHdbmua2/SqTp5QBdOW2dKfOBYBFRnE5LtP8zO7IQ8eaZYIHt7prNgGC7cb+DA3HR3Iqv1ClEUIYm3CIPIb5OmO6zDFMvXBOF27+/wP13JqisW4RRVAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/3edb94bf2dd34286db16ae23f7f74f9b/507e6/2025-06-16-cover.webp 200w,
/static/3edb94bf2dd34286db16ae23f7f74f9b/28a80/2025-06-16-cover.webp 400w,
/static/3edb94bf2dd34286db16ae23f7f74f9b/8d2ea/2025-06-16-cover.webp 800w,
/static/3edb94bf2dd34286db16ae23f7f74f9b/8aa8c/2025-06-16-cover.webp 1051w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/3edb94bf2dd34286db16ae23f7f74f9b/36ca5/2025-06-16-cover.png 200w,
/static/3edb94bf2dd34286db16ae23f7f74f9b/a3397/2025-06-16-cover.png 400w,
/static/3edb94bf2dd34286db16ae23f7f74f9b/a331c/2025-06-16-cover.png 800w,
/static/3edb94bf2dd34286db16ae23f7f74f9b/d99bd/2025-06-16-cover.png 1051w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/3edb94bf2dd34286db16ae23f7f74f9b/a331c/2025-06-16-cover.png&quot;
            alt=&quot;No alt text provided for this image&quot;
            title=&quot;No alt text provided for this image&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://www.linkedin.com/pulse/kafka-streams-state-store-rob-golder/&quot;&gt;https://www.linkedin.com/pulse/kafka-streams-state-store-rob-golder/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Every state store write also produces an event for this changelog. Now you have durability, but look at what your “index” has become:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The original message in the input topic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The state store update in RocksDB.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The changelog event for durability.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;This write amplification continues to grow. The changelog topic itself gets replicated across brokers for fault tolerance.&lt;/strong&gt; If you have multiple state stores for different query patterns, each needs its own changelog. A single business message might trigger a dozen physical writes across the system.&lt;/p&gt;
&lt;h2 id=&quot;dynamodbs-similar-challenge&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dynamodbs-similar-challenge&quot; aria-label=&quot;dynamodbs similar challenge permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;DynamoDB’s Similar Challenge&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The DynamoDB team at AWS faced a similar problem. DynamoDB’s storage is optimised for partition key access: you can retrieve any item instantly if you know its partition key.&lt;/strong&gt; The system achieves this through consistent hashing and partition-specific storage optimisation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But users wanted to query by other attributes.&lt;/strong&gt; &lt;em&gt;“Find all orders placed yesterday”&lt;/em&gt; or &lt;em&gt;“Get all items with status=‘pending’”&lt;/em&gt; required scanning entire tables, which was expensive and slow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DynamoDB’s solution was &lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html&quot;&gt;Global Secondary Indexes&lt;/a&gt;, or GSIs. The name suggests they’re just indexes, but the implementation tells a different story.&lt;/strong&gt; A GSI is actually a complete, separate DynamoDB table. It has its own partitions, capacity units, and replication. When you write to the main table, DynamoDB captures that change in DynamoDB Streams and asynchronously replicates it to each GSI.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/88c1f19c3a4c647a202295de2847f9c4/a331c/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAABJ0AAASdAHeZh94AAABUklEQVR42pWSO0sDURCF93fb2omFhRLSpEkRGxuxUIIgCkLEX2DUuMZ9ZXez72z2vo53RgUNscjAYWCY8zEz9zrYCGMM56qq0O/3MRgMMBwOMRqNWHVd/+nbDMfM7mHeH2HeJjDTW0C0oNZ2tUKWZQxumgZd10EIgdzWpNJA18A83cC8Wr/7APN8BzObwFFn+1AXR1DXPcirA1T+C1ohUZYFgiBAURRYr9do25bhtVUrFBpvCnm6B3V+CHnZgxmfoBofw8m9BPnHAnmYImsEg0QnGBBFEZIkYRCJ4Cs7OU1aVyXyKEPuxVYLFEFqews4WktoraCVzVLwHchAxjAMsVwuGU6i1emGUkruY6/RX/pmONsOyxNYI01Ia5dlyTDKv4FbH2VbUSnFRs/z4Lour/ozIUF3BlKQyfd9zOdzxHHMt0zTlKFa692B2/7af3/vN/ATKOGxz/UGAhoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/88c1f19c3a4c647a202295de2847f9c4/507e6/image-2.webp 200w,
/static/88c1f19c3a4c647a202295de2847f9c4/28a80/image-2.webp 400w,
/static/88c1f19c3a4c647a202295de2847f9c4/8d2ea/image-2.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/88c1f19c3a4c647a202295de2847f9c4/36ca5/image-2.png 200w,
/static/88c1f19c3a4c647a202295de2847f9c4/a3397/image-2.png 400w,
/static/88c1f19c3a4c647a202295de2847f9c4/a331c/image-2.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/88c1f19c3a4c647a202295de2847f9c4/a331c/image-2.png&quot;
            alt=&quot;How to use DynamoDB global secondary indexes to improve query performance  and reduce costs | AWS Database Blog&quot;
            title=&quot;How to use DynamoDB global secondary indexes to improve query performance  and reduce costs | AWS Database Blog&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://aws.amazon.com/blogs/database/how-to-use-dynamodb-global-secondary-indexes-to-improve-query-performance-and-reduce-costs/&quot;&gt;https://aws.amazon.com/blogs/database/how-to-use-dynamodb-global-secondary-indexes-to-improve-query-performance-and-reduce-costs/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It’s not an index in any traditional sense; it’s a derived table that happens to be automatically maintained.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The main table write.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DynamoDB Streams event.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Asynchronous writes to each Global Secondary Index table.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Your “index” needs its own capacity planning. It can throttle independently of your main table. Each GSI has its own replicas and its own failure modes. The write amplification is proportional to the number of query patterns you support.&lt;/p&gt;
&lt;h2 id=&quot;why-secondary-indexes-work-differently-in-distributed-systems&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-secondary-indexes-work-differently-in-distributed-systems&quot; aria-label=&quot;why secondary indexes work differently in distributed systems permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Secondary Indexes Work Differently in Distributed Systems&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;In a traditional single-node database, a secondary index is an additional data structure that enables the efficient retrieval of records without requiring a full table scan.&lt;/strong&gt; Think of a phone book: the main data is sorted by last name, but you might also want a separate index sorted by phone number. When you look up a phone number, you check the number index, find the person’s name, and then jump to their complete entry in the main book.&lt;/p&gt;
&lt;p&gt;Databases implement this using B-trees, which are like hierarchical directories. To find a value, you start at the root and follow pointers down through intermediate nodes until you reach the leaf containing your data. This gives you logarithmic lookup time: finding one record among a million takes only about 20 comparisons, not a million.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The crucial part is that in a single-node database, updating the main table and its indexes happens atomically.&lt;/strong&gt; When you change someone’s phone number, the database updates both the main record and the phone number index in one transaction. Either both succeed or both fail.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Distributed systems can’t work this way. You cannot atomically update multiple data structures across different nodes.&lt;/strong&gt; The network can partition, nodes can fail, and coordinating updates would require distributed transactions that destroy performance.&lt;/p&gt;
&lt;p&gt;Instead of fighting this constraint, both systems adopted eventual consistency. Secondary indexes become eventually consistent views of the primary data, maintained through asynchronous replication.&lt;/p&gt;
&lt;p&gt;These aren’t indexes in the traditional sense. They can be entirely different storage systems with their own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Partitioning schemes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Replication strategies&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consistency models&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Failure recovery procedures&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Capacity planning requirements&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When someone says “add a secondary index” to a distributed system, they’re really saying “build and operate another storage system”.&lt;/p&gt;
&lt;h2 id=&quot;how-these-indexes-actually-work&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-these-indexes-actually-work&quot; aria-label=&quot;how these indexes actually work permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How These “Indexes” Actually Work&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Starting with Kafka State Stores: when you query a state store, you’re querying that local RocksDB instance I mentioned.&lt;/strong&gt; The critical design decision is co-partitioning. Kafka ensures all events with the same key go to the same partition, and that partition is processed by one specific task. If you’re tracking user balances, all events for user “12345” go to the same place, and the state for user “12345” lives in the RocksDB instance on that same machine.&lt;/p&gt;
&lt;p&gt;This co-partitioning is elegant but creates operational complexity. When your application wants to query a user’s balance, it needs to know which machine holds that user’s state. Kafka maintains a directory of which keys live where through its group membership protocol. The Interactive Queries API uses this directory to route queries to the correct instance. During failures or rebalancing, queries might fail or return stale data from standby replicas.&lt;/p&gt;
&lt;p&gt;But what happens when that machine fails? That’s where the changelog topic comes in. Every update to the local RocksDB also produces an event to a special Kafka topic. If the machine dies, a new instance can rebuild the entire state by replaying this changelog. It’s durability through replication, but notice the cost: every state update is written twice, once to RocksDB and once to Kafka.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DynamoDB’s GSIs work through a similar async replication pattern but with different trade-offs.&lt;/strong&gt; Remember, a GSI is a complete DynamoDB table. When you write to the main table, DynamoDB captures that change in DynamoDB Streams (essentially a change data capture log) and asynchronously propagates it to your GSIs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Notice that word “asynchronously”.&lt;/strong&gt; Your write to the main table succeeds immediately, but the GSI updates happen later, typically within one to typically within one to two seconds under normal load (however this can stretch to minutes or more if the GSI is under-provisioned or the table is experiencing heavy writes). This lag is fundamental: updating synchronously would require coordinating across multiple partitions, which would significantly impact DynamoDB’s performance.&lt;/p&gt;
&lt;p&gt;GSIs can also be sparse, meaning only items with the indexed attribute appear in the index. If you index on “email,” items without an email field won’t exist in that GSI. This saves storage but means you need to understand which queries each GSI can actually serve.&lt;/p&gt;
&lt;p&gt;Both systems chose different points in the design space based on their constraints. Kafka prioritised keeping related data together through co-partitioning, accepting the complexity of query routing. DynamoDB prioritises flexibility, allowing GSIs to have completely different partition keys than the main table, while accepting higher eventual consistency lag and capacity management overhead.&lt;/p&gt;
&lt;h2 id=&quot;understanding-write-amplification&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#understanding-write-amplification&quot; aria-label=&quot;understanding write amplification permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Understanding Write Amplification&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Once you understand these architectures, you can calculate the true cost of query flexibility.&lt;/strong&gt; In DynamoDB, if you have a main table with three GSIs, every single item write becomes four separate writes: one to the main table and one to each GSI. Each of these writes is then replicated (typically three times) for durability. So your single logical write has become twelve physical writes across the infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kafka Streams faces a similar multiplication.&lt;/strong&gt; When a message arrives, you write it to the output topic (three replicas), update the local state store, and write to the changelog topic (another three replicas). If you maintain two different state stores for different query patterns, add another changelog topic. One business event can easily trigger nine or more physical writes.&lt;/p&gt;
&lt;p&gt;Each write serves a purpose: the original for durability, the indexes for queries, the replicas for fault tolerance. The amplification factor directly correlates with the query flexibility you’re providing.&lt;/p&gt;
&lt;h2 id=&quot;living-with-distributed-indexes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#living-with-distributed-indexes&quot; aria-label=&quot;living with distributed indexes permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Living with Distributed Indexes&lt;/h2&gt;
&lt;p&gt;Running these systems in production reveals their operational reality. The most critical metric becomes replication lag: how far behind are your indexes? In DynamoDB, your GSIs typically lag one to two seconds behind the main table. Under heavy write load, this can stretch to minutes. Kafka state stores face similar challenges during rebalancing, when partitions move between machines, and changelog replay can take hours.&lt;/p&gt;
&lt;p&gt;Because of this lag, you need to design applications differently. A user might update their profile and immediately query it through a GSI, retrieving their old data. Your code needs to handle this gracefully, perhaps by reading from the main table when freshness matters. Some teams implement “read your writes” patterns, tracking recent updates client-side.&lt;/p&gt;
&lt;p&gt;Capacity planning becomes multidimensional. It’s not enough to provision for your write rate; you need to account for the amplified rate across all indexes. A DynamoDB table handling 1,000 writes per second with three GSIs needs 4,000 write capacity units total. Miss this calculation, and your indexes will throttle, lag will increase, and queries will return increasingly stale data.&lt;/p&gt;
&lt;p&gt;Recovery scenarios add another layer of complexity. When a Kafka Streams instance crashes, its replacement must replay the entire changelog to rebuild state. For large state stores, this can take hours. DynamoDB faces similar challenges when adding a new GSI to an existing table: it must backfill the entire index, potentially processing billions of items.&lt;/p&gt;
&lt;h2 id=&quot;when-to-pay-the-secondary-index-tax&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#when-to-pay-the-secondary-index-tax&quot; aria-label=&quot;when to pay the secondary index tax permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;When to Pay the Secondary Index Tax&lt;/h2&gt;
&lt;p&gt;Given these costs, when do secondary indexes make sense in distributed systems? It depends on your alternatives. If users need to find orders by customer ID and your only option is scanning terabytes of data, the write amplification suddenly looks reasonable. If queries are rare or can tolerate minutes of latency, maybe a periodic batch job is better.&lt;/p&gt;
&lt;p&gt;You need to understand the full cost model from the start. Adding an index means multiple storage systems, eventual consistency in your application logic, multiplied infrastructure costs, complex recovery procedures, and new operational playbooks. Sometimes these costs are worth it. Sometimes you’re better off telling users to adjust their query patterns.&lt;/p&gt;
&lt;p&gt;Successful teams treat each secondary index as a long-term commitment. They prototype with real workloads to understand the amplification factor. They build tooling to monitor lag and alert on degradation. They educate developers about eventual consistency. Most importantly, they push back on speculative indexes, adding them only when the use case is clear and the alternative is untenable.&lt;/p&gt;
&lt;p&gt;This pattern appears everywhere in distributed storage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Elasticsearch adds doc values for analytics queries&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cassandra implements materialised views&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MongoDB maintains separate index structures per shard&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Even PostgreSQL separates heap and index storage&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;They all face the same constraint: you can’t modify specialised storage to support different query patterns without ruining its original performance characteristics.&lt;/strong&gt; So you build separate storage tiers, accept the write amplification, and manage the complexity.&lt;/p&gt;
&lt;p&gt;Each system optimises for its specific use case. Kafka keeps data co-partitioned, so related state lives on the same machine. DynamoDB allows completely different partitioning schemes between main tables and GSIs, maximising query flexibility. Elasticsearch builds multiple specialised structures for different query types. Each choice reflects the system’s priorities, but the fundamental trade-off remains the same.&lt;/p&gt;
&lt;p&gt;In distributed systems, there’s no such thing as “just” adding an index. There’s only building and operating another storage system, accepting the write amplification, and managing the complexity. Sometimes that trade-off is worth it. But now you understand why it’s a trade-off at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As users of those tools, we need to understand that there’s no magical glue. We can’t cheat physics.&lt;/strong&gt; Before we choose to use secondary indexing capabilities, we should analyse if we really need them, as they are usually also second-class citizens in those tools. They can still be a good choice, as they can cut our application code, but sometimes it’s just better not to bend the tool to scenarios that it wasn’t designed for and solve the challenge differently.&lt;/p&gt;
&lt;p&gt;I hope that this article will help you in better understanding what you’re signing for!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Do we still need the QA role?]]></title><description><![CDATA[Welcome to the new week! I’ve been seeing the same discussion popping up across tech forums and social media lately: is QA dead? “The…]]></description><link>https://event-driven.io/en/do-we-still-need-the-qa-role/</link><guid isPermaLink="false">https://event-driven.io/en/do-we-still-need-the-qa-role/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;I’ve been seeing the same discussion popping up across tech forums and social media lately: is QA dead?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“The testing market is ending.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Some argue.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“AI will handle whole testing soon.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Others chime in.&lt;/p&gt;
&lt;p&gt;This conversation has been going in circles for years, long before AI became everyone’s favourite scapegoat.&lt;/p&gt;
&lt;p&gt;I think that the real question isn’t whether AI will replace testers. It’s whether we’ve ever understood what good testing looks like.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 236px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/207fe23ea7e6c64ebd0600c41db54a56/71d48/image-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGQABAAIDAAAAAAAAAAAAAAAAAAEDAgQF/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB7cV3kpGpcGQP/8QAHBAAAgICAwAAAAAAAAAAAAAAARECAwAxEBIh/9oACAEBAAEFApy6gtjVrY4t8jg1/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAGBAAAgMAAAAAAAAAAAAAAAAAABAgIYH/2gAIAQEABj8CKjr/AP/EABoQAQADAAMAAAAAAAAAAAAAAAEAETEQIYH/2gAIAQEAAT8hiLBfvEKEeshohFQmwbCOJ//aAAwDAQACAAMAAAAQMA88/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPxAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPxAf/8QAHRABAAMAAwADAAAAAAAAAAAAAQARITFBsWFxkf/aAAgBAQABPxArrp5cvIfCXvESm/n2Uy3MezBde5xft9jJqQP2GltlcMtCLuf/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/207fe23ea7e6c64ebd0600c41db54a56/507e6/image-2.webp 200w,
/static/207fe23ea7e6c64ebd0600c41db54a56/52cce/image-2.webp 236w&quot;
              sizes=&quot;(max-width: 236px) 100vw, 236px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/207fe23ea7e6c64ebd0600c41db54a56/37402/image-2.jpg 200w,
/static/207fe23ea7e6c64ebd0600c41db54a56/71d48/image-2.jpg 236w&quot;
            sizes=&quot;(max-width: 236px) 100vw, 236px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/207fe23ea7e6c64ebd0600c41db54a56/71d48/image-2.jpg&quot;
            alt=&quot;Quality Assurance / Quality Control&quot;
            title=&quot;Quality Assurance / Quality Control&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-this-debate-even-exists&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-this-debate-even-exists&quot; aria-label=&quot;why this debate even exists permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why This Debate Even Exists&lt;/h2&gt;
&lt;p&gt;The reason we’re having this conversation at all comes down to how our industry has treated testing roles over the past decade. Companies marketed testing as the &lt;em&gt;“easy entry point”&lt;/em&gt; into the tech industry.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Anyone can click through an interface and report bugs. No coding required! Perfect for career changers.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This created an army of “clicker” testers who went through the same manual scenarios without understanding the underlying systems or business logic.&lt;/p&gt;
&lt;p&gt;When developers, especially backend developers, worked with these teams, they naturally concluded that testing was mindless work they could do better themselves.&lt;/p&gt;
&lt;p&gt;However, here’s the interesting psychological aspect. When someone tells you your code has bugs, you can feel that they’re criticising your work. The natural human response? Get defensive.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“What does this person know? I wrote this code, I understand it better than anyone.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s easier to dismiss the messenger than admit you might have missed something. &lt;a href=&quot;/en/killer-metrics-or-why-you-should/&quot;&gt;We’re again too attached to our code, treating bugs in it as a personal thing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developers (especially backend ones) work in clean, logical systems with well-defined inputs and outputs.&lt;/strong&gt; They write unit tests that verify their code works exactly as intended. From their perspective, what more could you need?&lt;/p&gt;
&lt;p&gt;Here’s what I’ve observed: developers often test what they built, not what should have been built. They verify their implementation matches their understanding of requirements. They rarely question whether those requirements made sense or whether their interpretation was correct.&lt;/p&gt;
&lt;p&gt;I’ve seen projects with 100% code coverage and comprehensive test suites still ship with obvious UX problems, business logic gaps, and integration issues that become apparent the moment real users interact with the system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;All tests passed, product still sucked.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-automation-illusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-automation-illusion&quot; aria-label=&quot;the automation illusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Automation Illusion&lt;/h2&gt;
&lt;p&gt;The push toward test automation made this worse. Automation is great for repetitive, well-understood scenarios, but there is a fundamental misunderstanding about what it can and cannot do.&lt;/p&gt;
&lt;p&gt;Automated tests catch regressions and verify that known scenarios continue to function as expected. They’re ineffective at identifying new problems, challenging assumptions, or understanding user needs. They test exactly what you tell them to test, nothing more.&lt;/p&gt;
&lt;p&gt;When developers write automated tests, they’re encoding their own assumptions about how the system should work. If those assumptions are wrong, the tests will happily pass while shipping broken experiences.&lt;/p&gt;
&lt;h2 id=&quot;qa-isnt-just-testing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#qa-isnt-just-testing&quot; aria-label=&quot;qa isnt just testing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;QA Isn’t Just Testing&lt;/h2&gt;
&lt;p&gt;Here’s one of the biggest misconceptions: QA equals testing. Tests are just one piece of quality assurance. Real QA thinking should start during design discussions, continue through development, and extend to release and beyond.&lt;/p&gt;
&lt;p&gt;This is not only testing, but ensuring a proper flow, user experience and correlation between functionalities.&lt;/p&gt;
&lt;p&gt;QA also ensures dedicated time and focus on exploring what can go wrong. As developers, we are often focused on the feature we are currently working on. Being in the zone, focused on delivering stuff, we may miss pieces that are essential for the overall quality.&lt;/p&gt;
&lt;p&gt;As a manager, I always tried to involve QA engineers in planning sessions from day one. Their input on early phases, with their different perspectives, brought significant value. It helped to strengthen the design before any code was written, which meant we could fix potential issues in the design rather than discovering them weeks later.&lt;/p&gt;
&lt;p&gt;This early involvement matters because it’s exponentially cheaper to fix problems in design than in code. When a test engineer identifies a user flow issue during design review, you adjust the flow and design accordingly. When they find it during testing, you’re looking at code changes, testing cycles, and delayed releases.&lt;/p&gt;
&lt;p&gt;Quality should be baked into every development stage, not bolted on at the end. A good test engineer helps teams think about edge cases during requirements gathering, advocates for testability during architecture discussions, and ensures quality considerations are part of every technical decision.&lt;/p&gt;
&lt;h2 id=&quot;dev-sec-qa-ops-pipe-dream&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dev-sec-qa-ops-pipe-dream&quot; aria-label=&quot;dev sec qa ops pipe dream permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dev-Sec-QA-Ops pipe dream&lt;/h2&gt;
&lt;p&gt;Of course, I’m not trying to say that developers are not responsible for finding similar flows. We do, we all do. Still, it’s about the specialisation. I’m questioning whether we can all be great at everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This reminds me of the backend versus frontend debate. In theory, everyone should be full-stack.&lt;/strong&gt; In practice, while most developers can work on both sides, very few are equally strong at both. The thinking patterns and skills are different enough that a healthy portion of specialisation usually produces better results.&lt;/p&gt;
&lt;p&gt;The same principle applies to quality assurance. Should every developer care about quality? Obviously. Should everyone write tests for their code? Of course. But does that mean we don’t need someone who specialises in thinking about quality?&lt;/p&gt;
&lt;p&gt;I don’t think so. Just as having a frontend specialist ensures better user experiences, even when backend developers can write JavaScript, having someone whose primary focus is on quality helps ensure better products, even when all developers prioritise testing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The key is integration, not separation. The most effective teams I’ve worked with had test engineers as full team members, not external consultants.&lt;/strong&gt; They participated in standups, reviews, architecture discussions, and retrospectives. They weren’t handed requirements to verify—they helped shape those requirements from a quality perspective.&lt;/p&gt;
&lt;p&gt;We don’t expect developers also to be designers, product managers, and infrastructure specialists. We recognise these require different ways of thinking.&lt;/p&gt;
&lt;p&gt;Testing, real testing, not mindless clicking, requires specialised knowledge. Understanding how users actually behave, designing test scenarios that uncover edge cases, and thinking about system-wide interactions. These are skills that take time to develop.&lt;/p&gt;
&lt;p&gt;The problem is that our industry confuses “anyone can click a button” with “anyone can design effective test strategies.” They’re completely different things.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’ll also highlight that we should not have a horizontal QA team. A horizontal responsibility split is a pathology, in my opinion.&lt;/strong&gt; I’ve never seen it working in the long term. We should aim to have self-organising teams capable of delivering the complete feature. I believe that also includes QA engineers.&lt;/p&gt;
&lt;h2 id=&quot;the-path-forward&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-path-forward&quot; aria-label=&quot;the path forward permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Path Forward&lt;/h2&gt;
&lt;p&gt;The solution isn’t eliminating testing roles or making developers responsible for everything. It’s stopping the hiring of unqualified people into testing positions and treating testing as the specialised engineering discipline it actually is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This means working with test engineers who can code, understand system architecture, automate routine tasks, and focus their intelligence on problems that actually require human intelligence.&lt;/strong&gt; More importantly, it means involving these specialists from the very beginning.&lt;/p&gt;
&lt;p&gt;When test engineers are embedded in teams from day one, participating in design discussions and helping shape requirements, the entire quality conversation changes. Instead of finding expensive problems after they’re built, you prevent them from being built at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developers also need to check their egos and recognise that having someone specifically focused on finding problems in their work is valuable, not threatening.&lt;/strong&gt; The best developers I know actively seek this feedback because they understand it makes their products better.&lt;/p&gt;
&lt;p&gt;The market for unqualified clickers is ending, and that’s good. But the market for skilled test engineers who think systemically about quality? That’s not going anywhere, AI or no AI.&lt;/p&gt;
&lt;p&gt;Our products are getting more complex, users are more demanding, and systems are more interconnected. Having someone whose job is thinking about all the ways things can go wrong - from the very beginning of development - isn’t a luxury. It’s basic common sense.&lt;/p&gt;
&lt;p&gt;Still, I get that our industry is not shaped by common sense and rationality, but by narratives and trends. It may be that GenAI would be a justification for CEOs to drop the QA Engineer role. Still, if that happens, our product quality and user experience will definitely be hurt by that.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Killer metrics, or why you should know upfront when to remove the new feature]]></title><description><![CDATA[Welcome to the new week! Let’s get back to the story I told in Why to measure and make our system observable? How to reason on chaotic world…]]></description><link>https://event-driven.io/en/killer-metrics-or-why-you-should/</link><guid isPermaLink="false">https://event-driven.io/en/killer-metrics-or-why-you-should/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;Let’s get back to the story I told in &lt;a href=&quot;https://www.architecture-weekly.com/i/150202094/a-clickbait-feature-goes-rogue&quot;&gt;Why to measure and make our system observable? How to reason on chaotic world&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I was working on another migration from on-premises to the cloud.&lt;/strong&gt; The product and sales teams decided to bait the current customers by exposing full-text capabilities for on-premise system search through the cloud. Teams added a change that continuously synchronised data from on-premises to the cloud. Then, it was indexed in Elasticsearch and exposed through the API on the web and mobile.&lt;/p&gt;
&lt;p&gt;Then boom bada bing, bada boom. Big announcement to all on-premise customers that they can now enable it for free and use it through our new Cloud services. Of course, most of the customers enabled it. For free is a decent price, right?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, great success?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/6ceb3074efe5cf9fff1069122a1b3a47/image-2.gif&quot; alt=&quot;&quot; title=&quot;borat-nein.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;We didn’t have the proper business metrics, so we have to add them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And guess what! Our full-text indexing of on-premises data appeared to account for around 90% (or more) of the spending.&lt;/strong&gt; Then we compared it with the business usage of the Cloud search API feature, and you know where it is going.&lt;/p&gt;
&lt;p&gt;Only one customer was using it. Rarely.&lt;/p&gt;
&lt;p&gt;Before I close the curtain, for this story, I’ll add that when we asked the client if they were okay with removing this feature, they immediately waved their hands and said: &lt;em&gt;“Yeah, kill it; we don’t need it”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If we’d done the metrics upfront, we’d still need to remove our feature, but we’d have prepared for that and done it earlier, without wasting so much money.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It’s yet another example that we’re wasting more time and code on maintaining things t’re not needed from a product perspective, instead of addressing the technical issues.&lt;/p&gt;
&lt;p&gt;We’re not wasting time coding too slowly; we’re wasting time doing stuff that doesn’t matter. How to do it better, then?&lt;/p&gt;
&lt;p&gt;Understanding the business and product scenery is crucial. Otherwise, we’ll be doing useless work. Understanding business is a foundation for the proper tradeoff analysis.&lt;/p&gt;
&lt;p&gt;So it’s not IT &lt;strong&gt;VS&lt;/strong&gt; Business but IT &lt;strong&gt;AND&lt;/strong&gt; Business, as IT &lt;strong&gt;IS&lt;/strong&gt; a Business nowadays.&lt;/p&gt;
&lt;p&gt;Once we realise and agree that working hand in hand with business is beneficial, we can start to collaborate on business metrics.&lt;/p&gt;
&lt;p&gt;We can define measurable metrics before starting, not just vague goals. Thanks to them, we can avoid redundant work and redundant costs, knowing when our feature wasn’t successful, and we can kill it.&lt;/p&gt;
&lt;p&gt;And that’s, of course, tricky, but we can learn that.&lt;/p&gt;
&lt;h2 id=&quot;killer-metrics-to-the-rescue&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#killer-metrics-to-the-rescue&quot; aria-label=&quot;killer metrics to the rescue permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Killer metrics to the rescue&lt;/h2&gt;
&lt;p&gt;Killer, not because they’re so great, but because we define when we can kill the feature.&lt;/p&gt;
&lt;p&gt;Let’s discuss it with the example when the business tells &lt;strong&gt;“build email digest feature”&lt;/strong&gt;. Perhaps they also provide more specifics on how it should work, you could start implementing it. Maybe that would even work, but without understanding why, you may end up in the same scenario as I’ve had with on-premises indexing and full-text search in the cloud.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you want to understand&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;The Why&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;, you could ask:&lt;/strong&gt; &lt;em&gt;“What specific user behaviour are we trying to change? What’s our current retention data? Do we know why users don’t return?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Business could answer:&lt;/strong&gt; &lt;em&gt;“Users sign up but forget about us. We want them to come back weekly.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;A bit better, but still not perfect, as it still tells more about &lt;em&gt;The What&lt;/em&gt; than &lt;em&gt;The Why&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You could follow up with:&lt;/strong&gt; &lt;em&gt;“Thank you for expanding, could you also tell me more on why do we want them to return more often?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Business could answer: “&lt;/strong&gt;It’s nice when people are buying stuff on our E-Commerce platform, but it’s much more efficient if those who buy goods have already repeated that. They’re much more likely to buy stuff than the totally new users. If we offer them customised weekly recommendations through email, they can make additional purchases.&lt;/p&gt;
&lt;p&gt;Now we’re talking! Still, we should not be satisfied with knowing The Why.&lt;/p&gt;
&lt;p&gt;We should also know The When. The When to kill this feature. Yes, before even starting to work on it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We could continue with:&lt;/strong&gt; &lt;em&gt;“Thank you, now I have a clear understanding on the need. I’d like to understand also more on the current use behavior. Do you know how often do users currently return? What engagement do our existing emails get? Do you know the research how email digests perform for similar products?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Business comes back:&lt;/strong&gt; &lt;em&gt;“In average, 40% return weekly now. Our newsletter gets 5% click-through. Found studies showing weekly digests increase return rates by 20-30%.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;You collaborate:&lt;/strong&gt;&lt;/em&gt; &lt;em&gt;“Great, thanks for clarification. What would we do if users started to cancel their subscriptions when they began receiving more emails? What would be the satisfying factor of increased traffic? What would we do if there’s no change? I’ll need to perform exact calculations, but we’ll need to use an external email provider that ensures proper delivery through spam filters, etc. That stuff costs. Plus, we’ll need to postpone other features we have in the pipeline.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Probably both you and the business will need to do their homework, math, etc. But the side effects of that are that you’re already starting to see the reality, so whether this feature is useful or not and whether there’s a financial reason to do it this way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One of the side effects is that you can kill this idea immediately or change the way you implement it.&lt;/strong&gt; Or you may come up together with an educated…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Killer metric&lt;/strong&gt;: &lt;em&gt;If weekly returns don’t improve by at least 5% within 2 months, or if the unsubscribe rate exceeds 8% within that timeframe, we kill it immediately.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And that’s great, as we’re investing in &lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;Removability over Maintainability&lt;/a&gt;. Thinking about &lt;a href=&quot;/en/residuality-theory-a-rebellious-take/&quot;&gt;Residuality, we discussed a week ago&lt;/a&gt;. You don’t need to maintain what you just removed!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/66560639426d51b62af2477421d3c35a/35b24/2025-06-02-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 60%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAMABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQBAgP/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABWuSJmRZ//8QAGhABAQACAwAAAAAAAAAAAAAAAQIAEQMSIf/aAAgBAQABBQLvWNurXcwPEBlvv//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABkQAAIDAQAAAAAAAAAAAAAAAAABEBExcf/aAAgBAQAGPwLWbD6VH//EABsQAQACAwEBAAAAAAAAAAAAAAEAESExgUFh/9oACAEBAAE/IRBWrWGpRvqd2WaaruKLGD7GOZ//2gAMAwEAAgADAAAAEFzf/8QAFxEBAAMAAAAAAAAAAAAAAAAAAAERMf/aAAgBAwEBPxCcW//EABcRAQEBAQAAAAAAAAAAAAAAAAEAETH/2gAIAQIBAT8QOuyX/8QAGxABAQEAAgMAAAAAAAAAAAAAAREAITFhcdH/2gAIAQEAAT8QA9LARPeQND2juRGwK+cBsgGI4lzRCBKvuJhM/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/66560639426d51b62af2477421d3c35a/507e6/2025-06-02-cover.webp 200w,
/static/66560639426d51b62af2477421d3c35a/28a80/2025-06-02-cover.webp 400w,
/static/66560639426d51b62af2477421d3c35a/8d2ea/2025-06-02-cover.webp 800w,
/static/66560639426d51b62af2477421d3c35a/4cbce/2025-06-02-cover.webp 833w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/66560639426d51b62af2477421d3c35a/37402/2025-06-02-cover.jpg 200w,
/static/66560639426d51b62af2477421d3c35a/4cda9/2025-06-02-cover.jpg 400w,
/static/66560639426d51b62af2477421d3c35a/c60e9/2025-06-02-cover.jpg 800w,
/static/66560639426d51b62af2477421d3c35a/35b24/2025-06-02-cover.jpg 833w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/66560639426d51b62af2477421d3c35a/c60e9/2025-06-02-cover.jpg&quot;
            alt=&quot;2025 06 02 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Still, you can think now:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;That’s great Oskar. I love it, but my business won’t cooperate.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;My business people would tell me: “Just build the email digest feature.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s, of course, a challenge, and it can be a showstopper. Still, we can try to do at least the homework on our side. Maybe we can’t gather all the data, but we can definitely gather some data.&lt;/p&gt;
&lt;p&gt;We can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check our analytics:&lt;/strong&gt; current user return patterns, email open rates, and signup-to-usage conversion&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check your SLA/SLO and customer commitments,&lt;/strong&gt; they can tell you what you have to fulfil.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mine production data:&lt;/strong&gt; server logs showing user behaviour patterns, telemetry data on feature usage, database queries to find drop-off points&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Look at 3-5 direct competitors:&lt;/strong&gt; Do they have similar features? How prominent are they?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;We could suggest a small experiment first:&lt;/strong&gt; “Let’s try manual weekly emails to 100 users for 3 weeks to see engagement before building the full feature”.&lt;/p&gt;
&lt;p&gt;Of course, we should be careful not to spend too much time so we’re not punished. Still, when we gather some, we can come back with real numbers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We could tell:&lt;/strong&gt; &lt;em&gt;“Based on my quick research on current usage data, 40% of users return weekly. Our logs show most users only check the app once after signup. Check also this link, this research shows that we can get above 5%. I checked the pricing of our e-mail providers and this will be the additional cost of sending more e-mails. What would you say if we tried sending manual weekly emails to 100 users for 3 weeks to see engagement before building the full feature? Then we could gather more data. Or if you’re sure then maybe we could target 5% improvement, and if that doesn’t happen in 2 months, we discuss whether to keep this feature?”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Managers like numbers, and when we speak to them using business terms. Even if they don’t like it when we push back, they appreciate it when they can use our numbers to discuss with their managers. As managers of managers, they appreciate it when people provide them with precise information.&lt;/p&gt;
&lt;p&gt;Of course, can’t tell that it works, but you can at least try, aye?&lt;/p&gt;
&lt;p&gt;So when business tells you &lt;a href=&quot;/en/bring_me_problems_not_solutions/&quot;&gt;“Bring me solutions, not problems!” - we should respond “Bring me problems, not solutions!“.&lt;/a&gt; Make them define the actual problem before you build anything.&lt;/p&gt;
&lt;p&gt;The positive outcome of such metrics and measuring usage is that when we discover we haven’t reached the expected outcomes, we may decide to drop this feature and not maintain it at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We should not treat removing features as our personal failure, but we should accept the evolution of our software.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We should accept that our current work has a finite lifespan and acknowledge that even if we remove something, it could bring us business value.&lt;/p&gt;
&lt;p&gt;This is the essence of prioritising removability. Success isn’t measured by how long code lasts, but by the value it creates during its lifetime&lt;/p&gt;
&lt;p&gt;If we understand our product and the environment we’re in, it’d be easier to break it down into removable parts.&lt;/p&gt;
&lt;p&gt;Funnily, if we remove stuff, then we won’t need to maintain, and those that are left will also be easier to maintain as they will have naturally shaped boundaries.&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;Removability over Maintainability&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/why-to-measure-and-make-our-system/&quot;&gt;Why to measure and make our system observable? How to reason on chaotic world&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/residuality-theory-a-rebellious-take/&quot;&gt;Residuality Theory: A Rebellious Take on Building Systems That Actually Survive&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/so-you-want-to-break-down-monolith/&quot;&gt;So you want to break down monolith? Read that first.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/tech-debt-doesnt-exist-but-trade/&quot;&gt;Tech Debt doesn’t exist, but trade-offs do&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Residuality Theory: A Rebellious Take on Building Systems That Actually Survive]]></title><description><![CDATA[I was sitting in a dark room at NDC Oslo when Barry O’Reilly started talking about lightbulbs. One hundred thousand of them, to be exact…]]></description><link>https://event-driven.io/en/residuality-theory-a-rebellious-take/</link><guid isPermaLink="false">https://event-driven.io/en/residuality-theory-a-rebellious-take/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;I was sitting in a dark room at NDC Oslo when Barry O’Reilly started talking about lightbulbs.&lt;/strong&gt; One hundred thousand of them, to be exact, all interconnected, each one either on or off. It was an odd way to start a talk about software architecture, but by the end, my mind was blown. What he presented wasn’t just another framework—it was a completely different way of thinking about how we build systems.&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/0wcUG2EV-7E?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;For years, I’ve advocated for proper risk analysis. I wrote about it in &lt;a href=&quot;/en/risk_of_ignoring_risks/&quot;&gt;“The Risk of Ignoring Risks”&lt;/a&gt;—how we need to think upfront about finding risks for our solutions, probability assessments, and mitigation plans. I still believe in all of that. But Barry’s Residuality Theory added a dimension I hadn’t considered.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Taking risks is okay as long as we’re prepared for them.&lt;/strong&gt; In the article mentioned, I explained the &lt;a href=&quot;https://en.wikipedia.org/wiki/Risk_register&quot;&gt;risk register&lt;/a&gt;, a tool that can be helpful for analysing risks. It is a simple table where we write down all risks associated with our solution.&lt;/p&gt;
&lt;p&gt;We write the probability in the lines and the degree of risk in the columns. By multiplying these two data, we get a result that tells us how much we should focus on a given risk. If we find out that our risks are very probable and the consequences of their occurrence are severe, we need to take action and make a corrective design decision upfront.&lt;/p&gt;
&lt;p&gt;If the risk is unlikely and has little consequence, we can treat it lightly. Usually, however, it is somewhere in the middle. We should write down what we will do when the risk occurs (e.g., when we have 100,000 requests per second).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Risk analysis assumes you can identify what might go wrong.&lt;/strong&gt; You list payment processor outages, network failures, scaling issues. You rate them by probability and impact. You prepare.&lt;/p&gt;
&lt;p&gt;But what about the things you never thought of? What about when TikTok makes your coffee shop go viral? What about when a health inspector shows up during your morning rush? What about when your competitor opens next door with half-price drinks?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Residuality Theory asks a different question.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“What risks should we prepare for?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It asks:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“What happens to our system when ANY stress hits it?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The difference is subtle but profound. Risk Matrices help us prepare for known unknowns, while Residuality Theory helps us build systems that survive unknowns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Barry O’Reilly developed the residuality theory, a rebellious approach to software architecture that focuses on designing systems that withstand unexpected stresses and changes.&lt;/strong&gt; This isn’t just philosophy. It is based on actual complexity science. Those 10,000 lightbulbs represent any complex system where components influence each other. The key insight is that systems move from stable (predictable but rigid) to a critical point, becoming chaotic (unpredictable cascading failures) as connections between components increase.&lt;/p&gt;
&lt;p&gt;Many software starts chaotic—everything depends on everything else. The goal is to reduce coupling and the chain reaction of failures, until you hit a sweet spot where your system can adapt without collapsing.&lt;/p&gt;
&lt;p&gt;Unlike traditional architectural approaches that design systems from a static, component-based perspective, &lt;strong&gt;Residuality Theory views software architecture as a collection of “residues”, the elements that survive after a system experiences stress.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fundamental concept of residuality theory is random simulation.&lt;/strong&gt; In a complex business system, it is impossible to accurately make point predictions about what will change, when it will change, how it will change, how often, and what things will change along with it.&lt;/p&gt;
&lt;p&gt;We can’t predict the future of our software systems, and anyone who claims they can should make you skeptical.&lt;/p&gt;
&lt;p&gt;In Residuality theory, instead of trying to guess what might go wrong, we deliberately imagine random scenarios that could break our system, even outlandish ones we don’t think will happen.&lt;/p&gt;
&lt;p&gt;Think of it as stress-testing your architecture with unexpected challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;What if our database provider goes bankrupt?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What if our main content creator removes all their material?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What if a regulatory change forces us to restructure our entire user data model?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What if Godzilla attacks our data centre? Yes, even that case.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By testing our architecture against a wide range of random stressors, we build systems that survive not just the problems we can anticipate, but also the ones we can’t imagine yet. Residuality theory pushes us past these mental blocks by making unexpected scenarios a central part of the design process.&lt;/p&gt;
&lt;p&gt;The result: software that withstands surprises better than traditionally designed systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Residuality Theory has four main concepts:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stressors&lt;/strong&gt; - Unexpected events that challenge your system (technical failures, market changes, regulatory shifts),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Residues:&lt;/strong&gt; The elements of your design that survive after stressors hit,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Attractors&lt;/strong&gt;: States that systems naturally tend toward when under stress,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Incidence Matrix&lt;/strong&gt;: A tool to visualise relationships between stressors and components&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The general process looks as follows:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Brainstorm a wide range of potential stressors (including extreme ones).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Map these stressors against your system’s components.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Identify which components are most vulnerable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Design solutions that will survive the stressors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Test your design against new, unforeseen stressors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rince &amp;#x26; repeat!&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;lets-try-it-by-example&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#lets-try-it-by-example&quot; aria-label=&quot;lets try it by example permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Let’s try it by example!&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a7ff9d93031840613771f62e626c1ee3/56dca/image-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABAAD/8QAFgEBAQEAAAAAAAAAAAAAAAAAAgAB/9oADAMBAAIQAxAAAAE6iYhJj2X/xAAYEAEBAQEBAAAAAAAAAAAAAAABAhEAA//aAAgBAQABBQLcJEj2d7Bltygrv//EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAEDAQE/Aaf/xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPwGH/8QAHRAAAgIBBQAAAAAAAAAAAAAAAAECMVEREnFyof/aAAgBAQAGPwJtXyNblmyPUwKPpq7Z/8QAGxABAAMBAAMAAAAAAAAAAAAAAQARIUExUWH/2gAIAQEAAT8hynHqWELVvJqK2F46RYrqsgbHzGxCroz/2gAMAwEAAgADAAAAENv/AP/EABYRAQEBAAAAAAAAAAAAAAAAAAABQf/aAAgBAwEBPxCaR//EABgRAAIDAAAAAAAAAAAAAAAAAAABESEx/9oACAECAQE/EIYUj//EAB4QAQEAAgEFAQAAAAAAAAAAAAERACExQVFxgbHB/9oACAEBAAE/EGVtMEGLP3L4bMQQJr3krHmh1e+Ta2/phREOTaOpfBhu6iXlrn//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/a7ff9d93031840613771f62e626c1ee3/507e6/image-2.webp 200w,
/static/a7ff9d93031840613771f62e626c1ee3/28a80/image-2.webp 400w,
/static/a7ff9d93031840613771f62e626c1ee3/8d2ea/image-2.webp 800w,
/static/a7ff9d93031840613771f62e626c1ee3/68fc1/image-2.webp 1200w,
/static/a7ff9d93031840613771f62e626c1ee3/43d96/image-2.webp 1600w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/a7ff9d93031840613771f62e626c1ee3/37402/image-2.jpg 200w,
/static/a7ff9d93031840613771f62e626c1ee3/4cda9/image-2.jpg 400w,
/static/a7ff9d93031840613771f62e626c1ee3/c60e9/image-2.jpg 800w,
/static/a7ff9d93031840613771f62e626c1ee3/6c738/image-2.jpg 1200w,
/static/a7ff9d93031840613771f62e626c1ee3/56dca/image-2.jpg 1600w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/a7ff9d93031840613771f62e626c1ee3/c60e9/image-2.jpg&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Imagine a local coffee shop chain building a mobile app where customers can order and pay for coffee in advance and pick it up when ready.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Goal is simple:&lt;/strong&gt; reduce wait times and in-store crowding.&lt;/p&gt;
&lt;p&gt;The traditional approach would be to design each component. We could come up with something like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Order Intake&lt;/strong&gt; - receiving orders,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Payment Processing&lt;/strong&gt; - charging customers,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kitchen Queue&lt;/strong&gt; - showing baristas what to make&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pickup Flow&lt;/strong&gt; - customer collection.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;With Residuality Theory, you start by imagining stressors, all kinds of them, even ridiculous ones.&lt;/strong&gt; The list could look as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Payment provider outage (Stripe API down)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Shop internet fails (router/ISP issue)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Barista drops tablet (screen shattered)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Monday 7:45 AM rush (40 orders in 15 minutes)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Customer no-shows (orders made but not collected)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Competing shop offers 50% off (customer flight)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Health inspector arrives during rush (disruption)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TikTok makes our “secret menu” viral (unexpected demand)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Next, you map what breaks when each stressor hits.&lt;/strong&gt; This can be eye-opening. When the shop’s internet fails, orders can’t come in, payments can’t be processed, the kitchen can’t see orders, and customers don’t get notifications. Everything fails together. The same is true for Monday rush - total system meltdown. Or when our local coffee chain goes viral &lt;em&gt;thanks to&lt;/em&gt; some TikTok influencer.&lt;/p&gt;
&lt;p&gt;The pattern becomes obvious. Your beautifully designed components are so tightly coupled that certain events cause complete collapse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In Barry’s terms, your system has the Attractor&lt;/strong&gt; - a state it naturally falls into - called “everything is on fire.” So yes, the negative one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can see that clearly when filling in the Incident Matrix:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 606px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/71cdffb33a22786fd9606f17d407927e/40234/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAABW0lEQVR42m2SaXaCUAyFWUl7nAAHUEEQVAbH1trufzkpXzxBbP2Rk7wk7+ZmcIoyk8vHXibTkQxH71JUuVyvR/VleSS321k220T2h51cv07y2cSyPNb3989FyiqT7S7VGNoxQBJWyVyW0UyiOFCNxKtQFsupJOniyc8bPV9MZJ1FGgvnY3HyzUrBqnoj05mrTP3xQFyvp2+1/b4EoS9eY4/cnuYQI8drYoChEQWs91spG6YkUdFAqQgQNtr8ljeeDFXowGJOul5q7wCTiJBENWNstvnvDL0GoK8gBq6A9F82i2B+OGeB137Etsp3pg9A8yMwfAI8HAsdMjPptgyIMet+wkfsZcucwPlSKyhgXYbduVlb1j7ztUVwCS1DAFkK58M8/y6FAq+WYhcAEFtuGTI7FgLtwfDt31KssgE/luK2M+QeW4Yw4/oRgI0JH20E2DZDK0TMWrZ/3OEvTEdRpjvQUXkAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/71cdffb33a22786fd9606f17d407927e/507e6/image-3.webp 200w,
/static/71cdffb33a22786fd9606f17d407927e/28a80/image-3.webp 400w,
/static/71cdffb33a22786fd9606f17d407927e/1a67c/image-3.webp 606w&quot;
              sizes=&quot;(max-width: 606px) 100vw, 606px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/71cdffb33a22786fd9606f17d407927e/36ca5/image-3.png 200w,
/static/71cdffb33a22786fd9606f17d407927e/a3397/image-3.png 400w,
/static/71cdffb33a22786fd9606f17d407927e/40234/image-3.png 606w&quot;
            sizes=&quot;(max-width: 606px) 100vw, 606px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/71cdffb33a22786fd9606f17d407927e/40234/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;States that systems naturally tend toward when under stress&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Total Meltdown&lt;/strong&gt; - Internet failure or viral rush affects all components (rows with 4s),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Payment-Order Death Spiral&lt;/strong&gt; - Payment and orders always fail together (coupled in 5/8 stressors),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kitchen Blindness&lt;/strong&gt; - Orders paid, but the kitchen can’t see them,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Waste Accumulation&lt;/strong&gt; - System works, but no-shows create hidden losses&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, here’s where it gets interesting. Traditional thinking says add redundancy, get backup internet, beefier servers, and more staff. With Residuality Theory, you should still consider that. Yet the focus is different: redesign so components can fail independently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The solutions, what Barry calls Residues, aren’t always technical.&lt;/strong&gt; That internet outage? Sure, you could get expensive backup connections.&lt;/p&gt;
&lt;p&gt;Or you could create “Internet Down Tuesday”, when systems fail. Walk-ins get 20% off, and the failure could be turned into a marketing event.&lt;/p&gt;
&lt;p&gt;Is the morning rush overwhelming everything? Don’t try to scale all systems. Create an express menu from 7 to 9 a.m. with only five drinks. A simpler menu means faster service and less system load. The constraint becomes a feature.&lt;/p&gt;
&lt;p&gt;Customers ordering but not picking up? Instead of eating the loss, create a 15-minute rule where unclaimed drinks go to waiting customers who need a pick-me-up. Waste becomes community goodwill.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After applying these changes, measure again. The tight coupling is broken.&lt;/strong&gt; The updated Incident Matrix could look as follows:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3f2bc96b1553c8e69dddf0e6b6d928fa/aaf9a/image-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 43.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABCUlEQVR42pWSaW6DQAyF5yrdwhoGEvYlkKTp/W/k8rkyQq1UKT8sb8/P9njcdGllnBqJkw8Zxlo+H7Pqcarldp+k7UqZl14uc6fxx9eyxs5CXdOeZLkO0g+V+mjX9aVcb6MUp1R8HkvmI0mPgWqEWF4kcszCLY+d58mWxy9WDHWubgrtQjCK3yUIXyWM3tQ/BC+SpAednji25YnhU7PHO8ZmHZuMpAGMhGJilocE23L4fq0H73gPCAlCijbbSGwSy0NCQyNEDO+q2uvKvAWA3xPaBP9NuMfrUbjiucz+TAjACq1oT4hYQ466rcz3KCu/Pb4d46k3XDdUQo7y8+8aJa1WoQk2xICekW//2BIKo6sPSgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/3f2bc96b1553c8e69dddf0e6b6d928fa/507e6/image-4.webp 200w,
/static/3f2bc96b1553c8e69dddf0e6b6d928fa/28a80/image-4.webp 400w,
/static/3f2bc96b1553c8e69dddf0e6b6d928fa/8d2ea/image-4.webp 800w,
/static/3f2bc96b1553c8e69dddf0e6b6d928fa/aa00f/image-4.webp 1033w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/3f2bc96b1553c8e69dddf0e6b6d928fa/36ca5/image-4.png 200w,
/static/3f2bc96b1553c8e69dddf0e6b6d928fa/a3397/image-4.png 400w,
/static/3f2bc96b1553c8e69dddf0e6b6d928fa/a331c/image-4.png 800w,
/static/3f2bc96b1553c8e69dddf0e6b6d928fa/aaf9a/image-4.png 1033w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/3f2bc96b1553c8e69dddf0e6b6d928fa/a331c/image-4.png&quot;
            alt=&quot;image 4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We added some new components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Technical:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Dual payment providers (Stripe + Square),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Receipt printer backup,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Staff hotspot failover.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Business:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;“Internet Down Tuesday” - 20% off walk-ins when the system fails,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Express menu 7-9 am (only 5 drinks available),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;15-minute auto-donation policy for unclaimed drinks&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;WiFi sharing agreement with the bookstore next door,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;“Regulars board” - an analogue backup for frequent customers&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Thanks to that, we not only tech-arounded the issue, but we also changed the overall process to be resilient.&lt;/strong&gt; Rush hour doesn’t crash everything because the express menu prevents overload. Internet outages don’t cascade because you’ve accepted failure as a valid state and made it profitable. Payment provider issues only affect payments, not the entire order flow.&lt;/p&gt;
&lt;p&gt;This isn’t about preventing failures. It’s about building systems that degrade gracefully, sometimes benefiting from failure. The express menu you created for rush hour could become your differentiator against competitors. The community cup program builds customer loyalty.&lt;/p&gt;
&lt;p&gt;If we repeat this analysis again and again, we’ll notice that we also have positive attractors. Some of the Residues that came up as a result of solving the stressors’ impact are also solving cases for the new ones!&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#summary&quot; aria-label=&quot;summary permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Summary&lt;/h2&gt;
&lt;p&gt;Of course, all of that requires collaboration between IT and business. Still, nowadays, IT is Business. The old split is obsolete already. If we don’t work together but remain in our positions, throwing the ball over the fence, we’ll play the telephone game instead of the real design.&lt;/p&gt;
&lt;p&gt;Design is not created in a vacuum.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don’t be an architecture astronaut. Don’t work in a vacuum.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In my opinion, Residuality Theory doesn’t kill risk analysis. We still need to find them, but without limiting ourselves and claiming that we know what we don’t know. It forces us to do our homework. What components fail together? What states does my system naturally fall into? What survives when things go sideways?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This approach acknowledges something important: we can’t predict everything. The world is messy&lt;/strong&gt;. Competitors appear, social media goes viral, inspectors show up, and equipment breaks. Instead of pretending we can plan for it all, we build systems that adapt and survive.&lt;/p&gt;
&lt;p&gt;We should stop pretending we can control everything.&lt;/p&gt;
&lt;p&gt;Risk analysis says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Prepare for what might happen.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Residuality Theory says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Build systems that handle what you didn’t prepare for.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Since that talk, I’ve started looking at systems differently. I started thinking more about the simulation aspect. It’s also kind of more aligned with how other architects work. The real architects work: before they build something, they perform stress analysis to understand if the building won’t fall.&lt;/p&gt;
&lt;p&gt;I’m not an expert in Residuality Theory. I hope to see Barry within the next two days at Techorama and that he won’t bash my humble notes here!&lt;/p&gt;
&lt;p&gt;You should check out Barry’s talk and read his book &lt;a href=&quot;https://leanpub.com/residuality?utm_source=architecture-weekly.com&quot;&gt;Residues: Time, Change, and Uncertainty in Software Architecture&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And most importantly, play with it, make dry runs, just like I did in this article. It’s a fun exercise that can bring you many insights into our work and your system design.&lt;/p&gt;
&lt;p&gt;When you build for chaos, you stop fearing it!&lt;/p&gt;
&lt;p&gt;Check also my other article in a similar spirit to what I described today:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;Removability over Maintainability&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/tech-debt-doesnt-exist-but-trade/&quot;&gt;Tech Debt doesn’t exist, but trade-offs do&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/bring_me_problems_not_solutions/&quot;&gt;Bring me problems, not solutions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/business-wont-let-me-and-other-lies/&quot;&gt;Business Won’t Let Me and other lies we tell to ourselves&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;Webinar with Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Underestimated Power of Hot Spots and Notes in EventStorming]]></title><description><![CDATA[I’ve been facilitating the EventStorming sessions for years now, and something keeps catching my attention. While teams naturally focus on…]]></description><link>https://event-driven.io/en/the-underestimated-power-of-hot-spots/</link><guid isPermaLink="false">https://event-driven.io/en/the-underestimated-power-of-hot-spots/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;I’ve been &lt;a href=&quot;/en/training/&quot;&gt;facilitating the EventStorming sessions&lt;/a&gt; for years now, and something keeps catching my attention.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While teams naturally focus on mapping out those orange event sticky notes (the backbone of any EventStorming session), they often &lt;strong&gt;underestimate the power of two critical elements: Hot Spots and Notes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For those less familiar with &lt;a href=&quot;https://www.eventstorming.com/&quot;&gt;EventStorming&lt;/a&gt;, it’s a workshop technique developed by &lt;a href=&quot;https://www.avanscoperta.it/en/trainer/a-brandolini/&quot;&gt;Alberto Brandolini&lt;/a&gt; that helps teams map complex business domains. You gather relevant people in a room, roll out a long paper on the wall, and start mapping out what happens in your system using different colored sticky notes. Orange ones represent domain events - the facts that have happened in your system and that domain experts care about. These events form the primary narrative of your process.&lt;/p&gt;
&lt;p&gt;Who are “relevant people”? Obviously, domain experts, decision makers, and we, technical people, who’ll be trying to solve business problems with 0s and 1s.&lt;/p&gt;
&lt;p&gt;If you’re looking for a quick intro, check this talk by Kenny Baas-Schwegler:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/WvkBKvMnyuc?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, no intros today, as today, I want to discuss the red and white sticky notes that don’t always receive the recognition they deserve.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-meetings-get-stuck&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-meetings-get-stuck&quot; aria-label=&quot;why meetings get stuck permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Meetings Get Stuck&lt;/h2&gt;
&lt;p&gt;Most of us have been in technical meetings that start with good intentions but descend into circular discussions. You gather smart people in a room to map out a process, but an hour in, everyone’s debating some detail nobody fully understands. Half the room checks emails two hours in, while the other half argues about edge cases.&lt;/p&gt;
&lt;p&gt;Or all people are engaged and moving along smoothly until we hit some complicated requirements. The discussion can quickly devolve into speculation. For example, nobody in the room knows the exact regulatory requirements, but instead of admitting this, people make conflicting assertions with increasing confidence. Forty minutes later, zero progress is made and the energy in the room is killed.&lt;/p&gt;
&lt;p&gt;I hit this problem repeatedly in my early EventStorming sessions until I started taking Hot Spots and Notes more seriously.&lt;/p&gt;
&lt;h2 id=&quot;hot-spots-to-the-rescue&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#hot-spots-to-the-rescue&quot; aria-label=&quot;hot spots to the rescue permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hot Spots to the rescue&lt;/h2&gt;
&lt;p&gt;Hot Spots are those bright red sticky notes that mark areas of uncertainty or disagreement.&lt;/p&gt;
&lt;p&gt;Notes, typically white, capture assumptions, decisions, or context discovered during the session.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 443px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5fce215d6d6c03b856f253acf98d4cd4/27348/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 102%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAADC0lEQVR42o2VS0xTWRjHSdyZuHBlXM2eiYlLYowycaFxJWKUUbGiAi3QYhEQKbY0QCZMgKIbzWRwErqi1vgAxWd8olJRx5pGsZoMPlCZYUIf99HH7c/T0s6gSMtJvpx7T77vd/7/L+eem8fckUiw2LFQZt63SZoWR1NltFAQ7d9pYpMfiPhfoT59inJ3BHX4Gor7PPJv/ahjz+YJmQVqWmpSR0aRek6KOEG4rYvQkTYUsw21phWpsgWpvIng/gYRhwmVGJFdQ8yt/x8Yn12QnWcIFhuQKo4gl1kI61p5u7+J18ZapusbmK6xEDEIeI2NkO4QyuD17EBpYJBQaS0zeitvmvS86y8k8OAHfJ58/MOrGWpby4WqIj5VNCKX1iEP31wIGJ8Fui4QLqll0nyYgVM/Ur1vCb82LsXauJLj5pV06JbTuiefKeFA2m1GuX4vO1B2CYU7TQSrWrjXsY0nt1Zww7mMgV/WcMW+lvvHVnG6YhMzB6yESw+i3nrIXIfzgaeHCAqgbDjKTKUFr30Xn4V1zw4D/RuK6C3YxIOtB4iKHgZ2C+DIWA6g+2IKKFUd5Z/yBvw7D/KmzMTNIh3uYiMXHZ18NFpQ9BaCe+pQR//MYdl9icDPJmLimDzcXkF9wTqa1hRiL1hPZ0kDXa5e0V8bsuhhYG896uPnOYBnLqUUKtVW3urqGNy4lzubDXiLTTzaped2eSXhymaUmiS0mYj3RY5zePYywVIzYQGUhe2o0UrUlJ6rrCjlzURFbx9tLcP90xbivleLULi9mnDySxCWgulI2pvRNxOob0e2OZhs7+X9sd+Jf/o7+6enjD4h1NJFuPcU0h8u5HNXUW6PEn3+kthf74lNTROXZFG/8CXy9eUgwHGhNtedk8xLpF1lBWak/1eQVB5Ph5aOZE6WHfPmb59IWdJEcTIyz5k518j73mIkEkFRlBQkGo2m2pDZIBaLLR6YabbT6cRut9PX14fD4cBms9Hd3U1PTw8+n++r3EUpHB8fx+Px4Pf7mZiYSL17vd4ULJHjN/EFJm54BKHLHMUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5fce215d6d6c03b856f253acf98d4cd4/507e6/image-2.webp 200w,
/static/5fce215d6d6c03b856f253acf98d4cd4/28a80/image-2.webp 400w,
/static/5fce215d6d6c03b856f253acf98d4cd4/1ea1b/image-2.webp 443w&quot;
              sizes=&quot;(max-width: 443px) 100vw, 443px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5fce215d6d6c03b856f253acf98d4cd4/36ca5/image-2.png 200w,
/static/5fce215d6d6c03b856f253acf98d4cd4/a3397/image-2.png 400w,
/static/5fce215d6d6c03b856f253acf98d4cd4/27348/image-2.png 443w&quot;
            sizes=&quot;(max-width: 443px) 100vw, 443px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5fce215d6d6c03b856f253acf98d4cd4/27348/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://github.com/ddd-crew/eventstorming-glossary-cheat-sheet?tab=readme-ov-file#core-concepts&quot;&gt;https://github.com/ddd-crew/eventstorming-glossary-cheat-sheet?tab=readme-ov-file#core-concepts&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What makes these tools valuable isn’t that they’re somehow more important than events (they’re not!), it’s that they give teams a structured way to handle the unknown without derailing progress.&lt;/p&gt;
&lt;p&gt;When I started explicitly encouraging teams to use Hot Spots, sessions immediately became more productive. Instead of pretending to know everything, team members would place a Hot Spot on the timeline whenever they hit uncertainty:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Not sure if this requires manual approval”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hot Spot!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Do we need to store this data for compliance?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hot Spot!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“System integration point unclear”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hot Spot!&lt;/p&gt;
&lt;p&gt;This small change had profound effects. Sessions that previously ground to a halt now maintained momentum. We could acknowledge gaps without getting stuck in them.&lt;/p&gt;
&lt;h2 id=&quot;the-power-of-documented-assumptions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-power-of-documented-assumptions&quot; aria-label=&quot;the power of documented assumptions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Power of Documented Assumptions&lt;/h2&gt;
&lt;p&gt;Using the derailed workshop example mentioned earlier, it could be handled differently if we put more effort into noting issues and assumptions.&lt;/p&gt;
&lt;p&gt;When the regulatory question was hit, someone could put a Hot Spot labelled:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Compliance requirements for trade verification?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then, a note was added stating:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Assuming 24-hour verification window is required for now.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;That ten-second action could save an hour of circular discussion.&lt;/strong&gt; We would document our uncertainty and our working assumption, then continue mapping.&lt;/p&gt;
&lt;p&gt;When the compliance team later clarified the actual requirement (e.g., 48 hours for certain trade types), we would have a clear record of how this impacted our model. The risk of proceeding with an assumption would still pay off by maintaining session momentum.&lt;/p&gt;
&lt;h2 id=&quot;if-its-not-on-the-board-it-doesnt-exist&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#if-its-not-on-the-board-it-doesnt-exist&quot; aria-label=&quot;if its not on the board it doesnt exist permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;“If It’s Not on the Board, It Doesn’t Exist!”&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I’ve watched too many important decisions evaporate as soon as meetings end.&lt;/strong&gt; A team spends twenty minutes reaching an agreement, but since nobody wrote it down, they end up having the exact same conversation weeks later.&lt;/p&gt;
&lt;p&gt;It’s worth having a rotation seat for the note-taker. Of course, everyone can (and should be putting them), but someone who focuses on ensuring that all agreements are on board is essential.&lt;/p&gt;
&lt;p&gt;Then, you’re ensuring that good, long discussions that end in consensus or compromise won’t be lost. Whenever something important is said or agreed upon, capture it on a white Note and put an arrow to which it relates. These can be invaluable during implementation, preventing numerous repeated debates.&lt;/p&gt;
&lt;p&gt;Thanks to that, it could save you some endless meetings. When you start to argue about something, someone can point to the wall and say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;”We already decided this three weeks ago.‘”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;working-around-missing-stakeholders&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#working-around-missing-stakeholders&quot; aria-label=&quot;working around missing stakeholders permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Working Around Missing Stakeholders&lt;/h2&gt;
&lt;p&gt;Let’s be honest, it’s hard to get everyone with crucial domain knowledge in the same room for a full day. Subject matter experts are usually the busiest people in the organization.&lt;/p&gt;
&lt;p&gt;I faced this with multiple teams redesigning their processes. Managers who understood the requirements could only be available in a short timeframe. Rather than trying to cram everything into that half-hour, you could run your session without him, but place Hot Spots everywhere we had questions only he could answer.&lt;/p&gt;
&lt;p&gt;Of course, that’s far from perfect. You’re risking that your assumptions will be invalidated and you demotivated. Still, when you’re not new to the domain, but have already spent some time, then those chances will get smaller. It’s still better to have progress than tell &lt;a href=&quot;/en/business-wont-let-me-and-other-lies/&quot;&gt;“Business Won’t Let Me!”&lt;/a&gt; or “We don’t have time for that!”.&lt;/p&gt;
&lt;p&gt;Management should appreciate your efforts and your respect for their time. The result could be that you could have a focused discussion when they finally get their time. Instead of wasting management’s limited availability with a rambling overview, you could point to specific Hot Spots:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“We need to understand exactly when reconciliation happens,”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Is manual approval required for settlements over €50,000?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Hot Spots should become a question collection mechanism, ensuring you make the most of limited stakeholder time.&lt;/p&gt;
&lt;h2 id=&quot;getting-stakeholders-interested&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#getting-stakeholders-interested&quot; aria-label=&quot;getting stakeholders interested permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Getting Stakeholders Interested&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;And here’s something unexpected I discovered: Hot Spots are great for bringing reluctant stakeholders into the process.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A team I was coaching couldn’t get their Product Managers to participate. They had mapped their system but were stuck on several critical decisions.&lt;/p&gt;
&lt;p&gt;Instead of giving up, they brought the EventStorming artefacts to his office, with Hot Spots clearly marking where they needed his input. Seeing the complexity visualised - and the specific places where his expertise was needed - changed his perspective entirely. The visual representation communicated the challenges in a way no meeting agenda could have.&lt;/p&gt;
&lt;p&gt;Not only did he help resolve our immediate questions, but they also joined the next session, bringing insights that significantly improved their model.&lt;/p&gt;
&lt;h2 id=&quot;making-this-work-in-practice&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#making-this-work-in-practice&quot; aria-label=&quot;making this work in practice permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Making This Work in Practice&lt;/h2&gt;
&lt;p&gt;If you want to get the most from Hot Spots and Notes in your sessions:&lt;/p&gt;
&lt;p&gt;Put the sticky notes where everyone can reach them. I distribute red and white stickies at multiple points along the modelling wall and explicitly tell participants (repeatedly!) they can add them anytime without asking permission.&lt;/p&gt;
&lt;p&gt;Start with more rather than fewer. It’s easier to close a Hot Spot that turns out to be simple than to recover from a derailed session because someone pretended to know something they didn’t.&lt;/p&gt;
&lt;p&gt;Review them periodically during the session. I typically pause after completing major process sections to look at accumulated Hot Spots. This helps determine which we can resolve immediately, which need investigation, and which might affect areas we haven’t modelled yet.&lt;/p&gt;
&lt;p&gt;Assign owners to post-session Hot Spots. Make sure someone is responsible for finding answers to unresolved questions before they become implementation blockers.&lt;/p&gt;
&lt;p&gt;Include Hot Spots and Notes in your documentation. These often contain the most crucial insights from the session - the areas of uncertainty or assumptions that could make or break your implementation.&lt;/p&gt;
&lt;h2 id=&quot;changing-how-teams-handle-uncertainty&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#changing-how-teams-handle-uncertainty&quot; aria-label=&quot;changing how teams handle uncertainty permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Changing How Teams Handle Uncertainty&lt;/h2&gt;
&lt;p&gt;What I value most about Hot Spots and Notes isn’t just how they improve workshops - it’s how they change team culture around uncertainty.&lt;/p&gt;
&lt;p&gt;In technical discussions, we often feel pressure to know everything immediately. Hot Spots create permission to say, &lt;em&gt;“I don’t know yet”&lt;/em&gt; while still making progress. They transform uncertainties from conversation-killers into clearly defined next steps.&lt;/p&gt;
&lt;p&gt;I’ve seen teams evolve from hiding what they don’t know to actively hunting for uncertainties as valuable information. Questions become assets rather than liabilities.&lt;/p&gt;
&lt;p&gt;If you’re incorporating EventStorming into your toolkit, don’t underestimate these powerful elements. They might not get as much attention as domain events, but in my experience, they often separate a productive modelling session from a frustrating stalemate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check also my other resources around EventStorming:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;Webinar about Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;Webinar with Radek Maziarka on Modularisation with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And &lt;a href=&quot;/en/fifteen_tips_on_how_to_run_meetings_effectively/&quot;&gt;15 tips on how to run meetings effectively&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Hints on getting to speak at conferences]]></title><description><![CDATA[Occasionally, I’m asked how to get to conferences (especially the bigger ones). My secret recipe is to: just try. Many people are so afraid…]]></description><link>https://event-driven.io/en/hints-on-getting-to-speak-at-conferences/</link><guid isPermaLink="false">https://event-driven.io/en/hints-on-getting-to-speak-at-conferences/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Occasionally, I’m asked how to get to conferences (especially the bigger ones).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My secret recipe is to: just try.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Many people are so afraid of being rejected that they’re paralysed.&lt;/strong&gt; Impostor syndrome is kicking in. Then thoughts: ” Those speakers know stuff much better,” or maybe “You need to know The People to get into those conferences.” That may be true, but…&lt;/p&gt;
&lt;p&gt;…but the magic is that there’s no magic. If you don’t try, if you won’t send abstracts, if you won’t be rejected numerous times, then you won’t learn and won’t get accepted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To prove it to you, look at the screens below.&lt;/strong&gt; I did them from &lt;a href=&quot;https://sessionize.com/&quot;&gt;Sessionize.com&lt;/a&gt; - the most popular application for sending conference abstracts. I have been nominated as the most active speaker for the last two years.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 600px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/36d8ecdcc52ebb3f4cb603e248fa70b7/3ad8d/2025-05-12-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 143.49999999999997%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAdABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAe7c6KCJoA//xAAVEAEBAAAAAAAAAAAAAAAAAAAgIf/aAAgBAQABBQJVf//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABUQAQEAAAAAAAAAAAAAAAAAADAx/9oACAEBAAY/Aon/xAAbEAABBAMAAAAAAAAAAAAAAAAAARARgSFRYf/aAAgBAQABPyEUSSnRFadDLW//2gAMAwEAAgADAAAAEPAFPP/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8QH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8QH//EAB4QAAICAgIDAAAAAAAAAAAAAAABESExYUGREIGh/9oACAEBAAE/EOcPgnC4eFmOwh1B6LMOhXuC9FpxIjbI2z//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/36d8ecdcc52ebb3f4cb603e248fa70b7/507e6/2025-05-12-cover.webp 200w,
/static/36d8ecdcc52ebb3f4cb603e248fa70b7/28a80/2025-05-12-cover.webp 400w,
/static/36d8ecdcc52ebb3f4cb603e248fa70b7/b0544/2025-05-12-cover.webp 600w&quot;
              sizes=&quot;(max-width: 600px) 100vw, 600px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/36d8ecdcc52ebb3f4cb603e248fa70b7/37402/2025-05-12-cover.jpg 200w,
/static/36d8ecdcc52ebb3f4cb603e248fa70b7/4cda9/2025-05-12-cover.jpg 400w,
/static/36d8ecdcc52ebb3f4cb603e248fa70b7/3ad8d/2025-05-12-cover.jpg 600w&quot;
            sizes=&quot;(max-width: 600px) 100vw, 600px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/36d8ecdcc52ebb3f4cb603e248fa70b7/3ad8d/2025-05-12-cover.jpg&quot;
            alt=&quot;No alternative text description for this image&quot;
            title=&quot;No alternative text description for this image&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 600px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/32e25c23c09dc5a4680d206c8398082a/3ad8d/image-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 137%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIBBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAe5U0GidygD/xAAUEAEAAAAAAAAAAAAAAAAAAAAw/9oACAEBAAEFAk//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAUEAEAAAAAAAAAAAAAAAAAAAAw/9oACAEBAAY/Ak//xAAcEAABAwUAAAAAAAAAAAAAAAAQACGBARFBUWH/2gAIAQEAAT8htxQWDbopGT//2gAMAwEAAgADAAAAEIsCDP/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8QH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8QH//EACEQAQABAgUFAAAAAAAAAAAAAAEAESExQVGRoWFxwdHx/9oACAEBAAE/EBGTiAGQZbSU68nudxvL6kx+IlD4gGbWBgvvAJQn/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/32e25c23c09dc5a4680d206c8398082a/507e6/image-2.webp 200w,
/static/32e25c23c09dc5a4680d206c8398082a/28a80/image-2.webp 400w,
/static/32e25c23c09dc5a4680d206c8398082a/b0544/image-2.webp 600w&quot;
              sizes=&quot;(max-width: 600px) 100vw, 600px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/32e25c23c09dc5a4680d206c8398082a/37402/image-2.jpg 200w,
/static/32e25c23c09dc5a4680d206c8398082a/4cda9/image-2.jpg 400w,
/static/32e25c23c09dc5a4680d206c8398082a/3ad8d/image-2.jpg 600w&quot;
            sizes=&quot;(max-width: 600px) 100vw, 600px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/32e25c23c09dc5a4680d206c8398082a/3ad8d/image-2.jpg&quot;
            alt=&quot;No alternative text description for this image&quot;
            title=&quot;No alternative text description for this image&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 600px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/706877f9fe9fc1c1a7f6f36460e60f62/3ad8d/image-3.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAEF/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAAB3KoB/8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQABBQJf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFRABAQAAAAAAAAAAAAAAAAAAEDH/2gAIAQEABj8CY//EABkQAAIDAQAAAAAAAAAAAAAAAAARARBhgf/aAAgBAQABPyHhEYLBBZX/2gAMAwEAAgADAAAAEAvP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGxABAQADAAMAAAAAAAAAAAAAAQARMVEhkaH/2gAIAQEAAT8QDg9QL5+LCbQRqYeX/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/706877f9fe9fc1c1a7f6f36460e60f62/507e6/image-3.webp 200w,
/static/706877f9fe9fc1c1a7f6f36460e60f62/28a80/image-3.webp 400w,
/static/706877f9fe9fc1c1a7f6f36460e60f62/b0544/image-3.webp 600w&quot;
              sizes=&quot;(max-width: 600px) 100vw, 600px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/706877f9fe9fc1c1a7f6f36460e60f62/37402/image-3.jpg 200w,
/static/706877f9fe9fc1c1a7f6f36460e60f62/4cda9/image-3.jpg 400w,
/static/706877f9fe9fc1c1a7f6f36460e60f62/3ad8d/image-3.jpg 600w&quot;
            sizes=&quot;(max-width: 600px) 100vw, 600px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/706877f9fe9fc1c1a7f6f36460e60f62/3ad8d/image-3.jpg&quot;
            alt=&quot;No alternative text description for this image&quot;
            title=&quot;No alternative text description for this image&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And hey, I should have also got the most rejected speaker badge.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So I’m not sure if I could give some advice on how to be accepted, but if I could, besides “just try”, then:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Work on the abstract and tell precisely what you want to show.&lt;/strong&gt; If you think that doing that will show too much, think twice about whether it’s a really good topic for a talk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.&lt;/strong&gt; &lt;strong&gt;Ensure that your talk is aligned with the conference leitmotif.&lt;/strong&gt; For instance: sending C# related talk to Java conference won’t help. Same goes for sending talk related to specific tool or technology to architecture track or modelling conference. Align your topic with the audience. Or just don’t send it there.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Don’t use GenAI tools to write it. Make a personal touch.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. When you write it, ask a few folks what they think and how they understand it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Make it engaging, not boring and not too lengthy.&lt;/strong&gt; Still, don’t make cheesy jokes, don’t be jumpy. You need to balance expressiveness with being on point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. If you don’t know how to do it, look at the abstracts of the talks that were accepted in previous years.&lt;/strong&gt; Don’t look at celebrities, but at folks who are closer to you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7. It’s best to back your abstract with links to your articles or recordings&lt;/strong&gt; so people can check how you explain stuff and what you’ll show.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;8. Don’t be afraid to be precise about your&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;special sauce&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt; Many submissions write something like “I’ll show you what solution worked out for us and how we solved it”. Even though you know what it was, and believe it was great, people selecting the talks don’t know it. Explain briefly the solution and why it’s worth showing, and use examples. If you’re afraid that you’ll show the audience too much of the essence in two sentences, then again, ask yourself if it’s really a topic for a talk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;9. Before getting to a big stage, it’s good to make your way through user groups and smaller conferences.&lt;/strong&gt; It helps you get used to the stage, gather feedback, hone your speaking skills, and get your name recognised. Having such experience will help you to be accepted.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;10. Don’t take rejection personally.&lt;/strong&gt; As you saw, I’m constantly rejected, there are conferences that are always rejecting me. Some conferences have hundreds of submissions, you not always need to have a good talk, good abstract but also luck. So it’s not about you, it may be about abstract, but not about you. If you want to get there: analyse what you coud do better, try again, rinse, repeat.&lt;/p&gt;
&lt;p&gt;**And most importantly: just try.&lt;/p&gt;
&lt;p&gt;Rejections will happen, but acceptance also. Sooner or later.**&lt;/p&gt;
&lt;p&gt;At least that’s how it was for me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And hey, if you’re at the conference, be it as an attendee or a speaker, I’m happy to talk with you in person.&lt;/strong&gt; I’ll be speaking at three conferences next month on three conferences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://techorama.be/&quot;&gt;Techorama&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://2025.eventcentric.eu/&quot;&gt;Event Centric&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://devoxx.pl/&quot;&gt;Devoxx Poland&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See you there?&lt;/p&gt;
&lt;p&gt;Cheers!&lt;br&gt;
Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why We Should Learn Multiple Programming Languages]]></title><description><![CDATA[In a recent Discord chat, I heard the phrase that: A good senior developer should be able to program in any language. As someone who’s spent…]]></description><link>https://event-driven.io/en/why-we-should-learn-multiple-programming/</link><guid isPermaLink="false">https://event-driven.io/en/why-we-should-learn-multiple-programming/</guid><content:encoded>&lt;p&gt;In a recent Discord chat, I heard the phrase that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;A good senior developer should be able to program in any language.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As someone who’s spent years working across multiple tech stacks, this got me thinking. Is language flexibility a core trait of senior engineers, or is deep specialisation more valuable?&lt;/p&gt;
&lt;p&gt;During my almost 18-year career, I’ve used numerous languages, such as C#, Java, JavaScript/TypeScript, C++, Ruby, PHP, Python, Perl, and ActionScript. I’ve also played with even more. I’m not flexing, I’m not a polyglot programming wizard. That’s just how it goes if you’re long enough in this industry.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Back in 1999, Andrew Hunt and David Thomas advised developers in “The Pragmatic Programmer” book to “learn at least one new language every year.”&lt;/strong&gt; I think that’s too far, as we should not fall into the trap of pushing ourselves too much, but… This wasn’t just about padding our resume but expanding our thinking. Different languages embody different approaches to solving problems, and exposure to these approaches enriches our toolbox regardless of your day-to-day language.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5949ccfc79a4479ac4581d630021abd0/6c738/2025-05-05-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQCAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAACA//aAAwDAQACEAMQAAABeUohF6AgMf/EABsQAAMAAgMAAAAAAAAAAAAAAAABAgMTERIh/9oACAEBAAEFAtqo7tVsxHoqVHB//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQIBAT8Bqv/EABoQAAICAwAAAAAAAAAAAAAAAAAQETEhIkH/2gAIAQEABj8CxJtRa6v/xAAcEAEAAgIDAQAAAAAAAAAAAAABABEhMUFRYfD/2gAIAQEAAT8hysT6Qs8tWfQljQ9tO4paDimecT//2gAMAwEAAgADAAAAEOvv/8QAFhEBAQEAAAAAAAAAAAAAAAAAAREA/9oACAEDAQE/ECBHTf/EABYRAQEBAAAAAAAAAAAAAAAAABEAcf/aAAgBAgEBPxBLbv/EAB0QAAICAwADAAAAAAAAAAAAAAERADEhUXFBYfD/2gAIAQEAAT8QK4lLo5H0aUBYO/vcfT8gCHwmITQcdkBkQHI52Y2TQNT/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5949ccfc79a4479ac4581d630021abd0/507e6/2025-05-05-cover.webp 200w,
/static/5949ccfc79a4479ac4581d630021abd0/28a80/2025-05-05-cover.webp 400w,
/static/5949ccfc79a4479ac4581d630021abd0/8d2ea/2025-05-05-cover.webp 800w,
/static/5949ccfc79a4479ac4581d630021abd0/68fc1/2025-05-05-cover.webp 1200w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5949ccfc79a4479ac4581d630021abd0/37402/2025-05-05-cover.jpg 200w,
/static/5949ccfc79a4479ac4581d630021abd0/4cda9/2025-05-05-cover.jpg 400w,
/static/5949ccfc79a4479ac4581d630021abd0/c60e9/2025-05-05-cover.jpg 800w,
/static/5949ccfc79a4479ac4581d630021abd0/6c738/2025-05-05-cover.jpg 1200w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5949ccfc79a4479ac4581d630021abd0/c60e9/2025-05-05-cover.jpg&quot;
            alt=&quot;Tower of Babel - Wikipedia&quot;
            title=&quot;Tower of Babel - Wikipedia&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Pieter%5C_Bruegel%5C_the%5C_Elder%5C_-%5C_The%5C_Tower%5C_of%5C_Babel%5C_(Vienna)%5C_-%5C_Google%5C_Art%5C_Project.jpg&quot;&gt;https://commons.wikimedia.org/wiki/File:Pieter\_Bruegel\_the\_Elder\_-\_The\_Tower\_of\_Babel\_(Vienna)\_-\_Google\_Art\_Project.jpg&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Without this exposure, we can end up “stewing in our own juices,” unable to see alternatives. I’ve witnessed this repeatedly, for instance, with backend developers who construct elaborate workarounds to avoid touching JavaScript, not realising they’re making their lives harder, not easier.&lt;/p&gt;
&lt;p&gt;And hey, I’ve been there, I’ve been a JavaScript hater, even worse for TypeScript, and I left the project when I was forced to code in Perl. Still, I transitioned, and I’ve made my journey.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Looking back, each time I’ve learned a new language, I’ve brought back new insights to my primary stack.&lt;/strong&gt; I just believe that having proficiency in more than just your favourite language opens doors to new ways of thinking.&lt;/p&gt;
&lt;h2 id=&quot;learning-the-language-vs-speaking-it-fluently&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#learning-the-language-vs-speaking-it-fluently&quot; aria-label=&quot;learning the language vs speaking it fluently permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Learning the Language vs. Speaking It Fluently&lt;/h2&gt;
&lt;p&gt;When developers switch languages, they bring their habits with them, for better or worse.&lt;/p&gt;
&lt;p&gt;Look at TypeScript code written by longtime Java or C# developers. You’ll often see unnecessary class hierarchies, dependency injections, and repository patterns when simpler approaches would work better. They’ve learned the syntax but not the philosophy.&lt;/p&gt;
&lt;p&gt;I’ve watched .NET and Java programmers struggle with Node.js because they’re trying to force it into patterns that don’t fit. Node.js is architecturally more similar to Go’s minimalism than enterprise C# or Java, but they don’t see that. They’re writing C# with a different syntax.&lt;/p&gt;
&lt;p&gt;It’s like spoken languages - learning classroom English is different from speaking conversational English. Learning syntax doesn’t mean you’re writing idiomatic code that leverages the language’s strengths.&lt;/p&gt;
&lt;p&gt;Transitioning takes time. You might learn Go’s syntax in a week, but understanding its ecosystem and idioms is a much longer journey. Most people reach a productivity plateau after 3-6 months, but true fluency takes longer.&lt;/p&gt;
&lt;h2 id=&quot;when-it-makes-sense-to-add-a-new-language&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#when-it-makes-sense-to-add-a-new-language&quot; aria-label=&quot;when it makes sense to add a new language permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;When It Makes Sense To Add A New Language&lt;/h2&gt;
&lt;p&gt;I’ve seen cases where adding a new language to the stack was worth it. The reasons were rarely related to the specific technologies, but rather to the state of the industry.&lt;/p&gt;
&lt;p&gt;There are legitimate cases where a new language makes sense:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When the specific business problem demands it&lt;/strong&gt;. Sometimes, specific services benefit from being written in a particular language. For example, when working with Node.js and needing CPU-intensive operations (like TypeScript migrations), it makes sense to use a more performant language for that specific component. See more in &lt;a href=&quot;/en/typescript-migrates-to-go-whats-really/&quot;&gt;TypeScript Migrates to Go: What’s Really Behind That 10x Performance Claim?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When it helps with hiring and retention&lt;/strong&gt;, I’ve had several clients who migrated from C#/Java to Node.js because they found it easier to hire developers. The systems they were building didn’t actually require the ceremonial complexity that C#/Java provides; there was no business advantage beyond familiarity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When it opens up new career opportunities&lt;/strong&gt;. I personally benefited from refreshing my Java skills at one point. Most of my clients are Java, so that investment has paid off significantly in my consulting work. Some languages are also becoming obsolete. They’re still needed, but the market is shrinking (cheers, Cobol!).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But let’s be honest - not every technology experiment belongs in production.&lt;/strong&gt; The key question is rarely&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Can we write this in X?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;but&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Who will maintain this X code long-term?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I once had to deal with a pricing algorithm module written in F#. While I actually like F# a lot, the decision wasn’t justified in this context. One of my first decisions after taking over was painfully rewriting it in C#. The marginal benefits didn’t outweigh the cognitive load on the rest of the team. It was a classical example of CV-driven design. The company burned money on a rewrite. But it was still cheaper than hiring or teaching new people F# to maintain it.&lt;/p&gt;
&lt;p&gt;In another case, a team introduced a Python microservice because they thought it would be faster to develop. It turned out to be slower, leading to yet another rewrite. The business wasn’t even aware they were paying for learning experiments and subsequent cleanup rather than actual features.&lt;/p&gt;
&lt;p&gt;These failures usually happen because people don’t ask the hard questions:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;What’s the actual business driver for this change?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Is it worth spending 3-6 months of reduced productivity while the team learns something new?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Who will maintain this code if the champion leaves?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even with good intentions, mixing technologies creates maintenance challenges. Small integrations in different technologies become business-critical risks. When key people leave, what should be a 15-minute fix can turn into weeks of investigation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why such decisions usually need to be consulted and confirmed at a higher level than those of the inner teams.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;cross-the-frontend-backend-divide&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cross-the-frontend-backend-divide&quot; aria-label=&quot;cross the frontend backend divide permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cross the Frontend-Backend Divide&lt;/h2&gt;
&lt;p&gt;There’s one language-learning area I consider especially important: the frontend-backend divide.&lt;/p&gt;
&lt;p&gt;If you’re a backend developer, learning some frontend technologies means you won’t limit yourself to just half of the feature delivery. You can help the frontend team when needed and understand the whole system better.&lt;/p&gt;
&lt;p&gt;Similarly, frontend developers benefit from understanding what happens on the server. I’ve noticed backend developers often go to extreme lengths to avoid writing JavaScript, even when it’s the simplest solution. This resistance ultimately makes their job harder, not easier.&lt;/p&gt;
&lt;p&gt;You don’t need to be an expert on both sides, but having enough knowledge to collaborate effectively improves everyone’s lives.&lt;/p&gt;
&lt;p&gt;Going back to our English classroom example. As we learn it to be able to communicate, learn in our projects and not be limited only to our native language projects and resources. We should learn both about the backend and frontend stack and the frontend, so as not to limit ourselves. We still can specialise in one of them, but you should be able to deliver a feature end-to-end, getting a little help from others, instead of waiting for them.&lt;/p&gt;
&lt;p&gt;See also my interview with Tomasz Ducin on &lt;strong&gt;Frontend Architecture, Backend Architecture or just Architecture?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;but-im-an-architect---do-i-still-need-to-learn-new-languages&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#but-im-an-architect---do-i-still-need-to-learn-new-languages&quot; aria-label=&quot;but im an architect   do i still need to learn new languages permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;“But I’m an Architect - Do I Still Need to Learn New Languages?”&lt;/h2&gt;
&lt;p&gt;My answer: &lt;strong&gt;absolutely, perhaps even more so.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Architecture decisions have long-lasting consequences. If you’re not hands-on with the technologies you recommend, how can you truly understand their tradeoffs? You’ll end up making decisions based on blog posts and conference talks rather than real-world experience.&lt;/p&gt;
&lt;p&gt;The best architects I know still code regularly, even if not full-time, on production systems. They prototype in different languages, build proofs of concepts, and occasionally dive into codebases to understand pain points. They recognise that staying technically sharp is part of the job.&lt;/p&gt;
&lt;p&gt;Without practical knowledge of multiple languages and ecosystems, how can you evaluate when a new technology is appropriate versus when it’s just adding unnecessary complexity? How do you distinguish between legitimate technical concerns and simple resistance to change?&lt;/p&gt;
&lt;p&gt;Architects who stop learning new technologies quickly lose touch with the realities of modern development, and their teams suffer for it.&lt;/p&gt;
&lt;p&gt;Read more in my &lt;a href=&quot;/en/architect_manifesto/&quot;&gt;Architect Manifesto&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;finding-your-balance&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#finding-your-balance&quot; aria-label=&quot;finding your balance permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Finding Your Balance&lt;/h2&gt;
&lt;p&gt;So where does this leave us? Should every senior developer be capable of programming in any language? I think that’s overstating it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s more like: a senior developer should have the mental models to adapt to new languages relatively quickly, while recognizing that true mastery takes time and immersion.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The challenge isn’t the language syntax but the frameworks, libraries, and idioms around it. You might learn Go in a week, but that doesn’t mean you’re writing idiomatic Go code. You’re likely writing Java-style Go, which misses the point entirely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For organizations, the calculus is a bit different:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;For stable, long-term projects:&lt;/strong&gt; Maintaining technological consistency usually trumps introducing new languages&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;For teams facing specific technical challenges&lt;/strong&gt;: Targeted use of specialized languages may be worth the investment&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;For everyone:&lt;/strong&gt; Having a process for evaluating when to introduce new technologies is crucial&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key business question often gets overlooked: What’s the actual driver for introducing a new language? Is it worth spending 3-6 months of reduced productivity while a team learns something new? Sometimes yes, but often no.&lt;/p&gt;
&lt;p&gt;The question shouldn’t be&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Can we write this in X?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;but,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Who will maintain this X code long-term?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;investing-in-growth-mindfully&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#investing-in-growth-mindfully&quot; aria-label=&quot;investing in growth mindfully permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Investing in Growth, Mindfully&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Learning new languages represents an investment, both personal and organisational.&lt;/strong&gt; While syntax might come quickly, ecosystem familiarity takes longer. Even after spending years exploring a new language, many developers don’t feel ready to join a project in that language at a senior level.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yet every senior developer I respect maintains curiosity about other approaches.&lt;/strong&gt; They might not be experts in five languages, but they’re familiar enough with alternatives to know when their primary tool isn’t the best fit.&lt;/p&gt;
&lt;p&gt;This isn’t about chasing trends or padding resumes. It’s about expanding your mental models so you can recognize patterns across languages and ecosystems. It’s about understanding that the “obvious solution” in your primary language might be fighting against the grain in another.&lt;/p&gt;
&lt;p&gt;And perhaps most importantly, it’s about maintaining the humility to recognize when you’re forcing a problem into your favorite solution rather than finding the right approach for the challenge at hand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Because ultimately, programming languages are tools, not identities.&lt;/strong&gt; The best developers I know don’t call themselves “Java programmers” or “JavaScript developers”; they’re problem solvers who use the right tool for the job, whether it’s the one they’ve used for a decade or the one they started learning last month.&lt;/p&gt;
&lt;p&gt;What are your thoughts?&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Business Won't Let Me and other lies we tell to ourselves]]></title><description><![CDATA[“Business won’t let us implement proper telemetry.” “We can’t write tests because there’s no time.” “The manager won’t approve refactoring…]]></description><link>https://event-driven.io/en/business-wont-let-me-and-other-lies/</link><guid isPermaLink="false">https://event-driven.io/en/business-wont-let-me-and-other-lies/</guid><content:encoded>&lt;blockquote&gt;
&lt;p&gt;“Business won’t let us implement proper telemetry.”&lt;/p&gt;
&lt;p&gt;“We can’t write tests because there’s no time.”&lt;/p&gt;
&lt;p&gt;“The manager won’t approve refactoring.”&lt;/p&gt;
&lt;p&gt;“We don’t have budget for proper CI/CD.”&lt;/p&gt;
&lt;p&gt;“The deadline doesn’t allow for documenting the API.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;These excuses appear in development teams with the regularity of standups and coffee breaks. You’ve heard that numerous times, and I bet you also said it. Why? Because I did both.&lt;/p&gt;
&lt;p&gt;Those phrases reveal a fundamental confusion about where business decisions end and technical decisions begin. They often mask our reluctance to take responsibility for our actions.&lt;/p&gt;
&lt;h2 id=&quot;whose-decision-is-it-anyway&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#whose-decision-is-it-anyway&quot; aria-label=&quot;whose decision is it anyway permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Whose Decision Is It Anyway?&lt;/h2&gt;
&lt;p&gt;Let’s cut to the chase: technical experts should make technical decisions. That’s us. Or at least that’s our role.&lt;/p&gt;
&lt;p&gt;You don’t ask your manager for permission to create a new file, name a variable, or start a debugging session. You just do it. So why are we asking permission for implementing telemetry, writing tests, or cleaning up code?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Delivering a feature doesn’t end with written code; this is just a part of it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1ef11aa0b54de06a0df63bd0ebfaac93/1a152/2025-04-28-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsTAAALEwEAmpwYAAAELklEQVR42lWSaVNaZxTH+QJ90c/TF20/QV91ptNl0hdNjU1iTeIkuNS2mQ6EqLUTJdEoxCqiJkRUEEHBC9zLIsi+XO4FLotsst6Nyy59iM00nTnzzJl5zm/+55z/4bVarfRFNhojEsk0QSQZlsvlCv5AKJW6yOcLpXKld9Vvd3rgrZF0JpPLF4ogisVyu93msSydSQUyKX864b1I+koFDCTJuBvkybg/RQSoKlEtxahqIncRRIMWHLXHIg4Cd9E0yaOpSpMOdbnIdXTY8HXSb2GlvLeU8/Q4tM2GQXTraK+J9xrYVRNvMWEADuB6LdhmIy0GbdL/RoMC1ehl1lPMerr1SJNBWyzaYkBNBPyCl60FBzD1AQwCMJ061mtG+61Y8RrmMNBaiwmV8+dhn46p+EEBQKgPYLRdxzgqRJVDlbwr6NEmceM7ZW+njtJVvJjHckkrWfICZaD0HibBzGi14PZ7DotlgqLoZNRWzjnosq9w4a6VQlhAR8TDYPOXGQcYoUGF/gc3mEijcPZoY+vTo1Q64WOpSLeVuGpGLzPuajF4GYM+mZEvnvn6XJhlsA4X/Q8ebLuO9xnvF8/ffLTshlRLNrU4aNlsgyELfqBMZywf/7J1Y9+GQ6uWw6W4Z7/L4WCFg4XVauUEjhQzjg2ZeFr4+/7qtEr4jWrxXjZhjWPWBG4tX3pml58JxXPH0nGN6GuDXFDIuDKErVYt8WrVCmLWWmCd06Y/t+lhSG3S75shNWI+siD6v7cUshmB9umUWshfX1x4rdyzIxorcgybjgDIo+lqk4n0OBxM0q2DA4h1uVi7joOZK4XAyB9LI7f4s0N3hT/eHhPIdiBnlnBUCv42i9FUdbAw4Pg7hwfWkyU/OClwYT0uAnwanpz7amL55xXTyEvoB5FCZzKYTvXJuAPUk7X3PgMPqLJ/+cAi3LbZPbYE4WRqQXCb959IHkmRB89UP4l2BOsnBA4vSGUer7lJBoDqAAaC/TbmCzv5a2dT62eTr+Df5M6VAzibdI6JJLfH58eebk7K3BKVmav6Clm3HdlNx5E6Sw7aZipBphLNpM+nl3aHRIoHi+pxKTIuMdld1nu//vn90MR3o4K788oxsSaW9IE7ZcqeJhUcKDNkyQhr3Cher/hXd97cEa4NC2TT6whfAqtPTQ9Fi3eeH/Ff6m8+fnXryfbWgWp+BwItBCNOjq3y+l364Zr88w0zlTvzeU4w1DwlNY4+h4bn9vSw48bjm5+N8kfm1PeXoImFbeXB9tiM9MvJTfHrE44p8zoN6q1W8e2MWLk8qf5rCHcpLY5TpV4P2w0EhuweKublh5JdjQ46mZdvzL6YOdkVC1Z2Nvf2WLrCI8kaDOmOVQq95q1hf9Ns1J47jH437HIYbVaTw2aCTVqLGZyQATrVbW1ITo8UCGywwHqSrP0DOr2/4FTt3RcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/1ef11aa0b54de06a0df63bd0ebfaac93/507e6/2025-04-28-cover.webp 200w,
/static/1ef11aa0b54de06a0df63bd0ebfaac93/28a80/2025-04-28-cover.webp 400w,
/static/1ef11aa0b54de06a0df63bd0ebfaac93/8d2ea/2025-04-28-cover.webp 800w,
/static/1ef11aa0b54de06a0df63bd0ebfaac93/68fc1/2025-04-28-cover.webp 1200w,
/static/1ef11aa0b54de06a0df63bd0ebfaac93/43d96/2025-04-28-cover.webp 1600w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/1ef11aa0b54de06a0df63bd0ebfaac93/36ca5/2025-04-28-cover.png 200w,
/static/1ef11aa0b54de06a0df63bd0ebfaac93/a3397/2025-04-28-cover.png 400w,
/static/1ef11aa0b54de06a0df63bd0ebfaac93/a331c/2025-04-28-cover.png 800w,
/static/1ef11aa0b54de06a0df63bd0ebfaac93/8537d/2025-04-28-cover.png 1200w,
/static/1ef11aa0b54de06a0df63bd0ebfaac93/1a152/2025-04-28-cover.png 1600w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/1ef11aa0b54de06a0df63bd0ebfaac93/a331c/2025-04-28-cover.png&quot;
            alt=&quot;2025 04 28 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://www.monkeyuser.com/2020/feature-complete/&quot;&gt;https://www.monkeyuser.com/2020/feature-complete/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Like in the comic above, we often treat all other aspects as annoyances. That’s also how we explain it to the business people.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Besides typing words on the keyboard, we should ensure that they make sense, form a feature of proper quality, that we can safely deploy them, and then later observe their behaviour. So testing, adding telemetry, etc., is part of the coding process; it shouldn’t be treated as some tedious additional stuff we need to do.&lt;/p&gt;
&lt;p&gt;Mark Seeman’s article &lt;a href=&quot;https://blog.ploeh.dk/2019/03/18/the-programmer-as-decision-maker/&quot;&gt;“The Programmer as Decision Maker”&lt;/a&gt; nails this point. As developers, we make hundreds of technical decisions daily. Every variable name, code structure, and design pattern choice is a technical decision we make without consulting management. And rightly so. That’s not just part of our job, it IS our job. We’re paid to apply our expertise to solve technical problems. We need to start acting like it.&lt;/p&gt;
&lt;h2 id=&quot;requirements-not-permissions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#requirements-not-permissions&quot; aria-label=&quot;requirements not permissions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Requirements, Not Permissions&lt;/h2&gt;
&lt;p&gt;The key distinction lies in understanding the difference between requirements and implementation details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Business stakeholders should provide requirements and constraints, such as features, budgets, priorities and deadlines.&lt;/strong&gt; They represent the “what” and “when” of our work, but they shouldn’t dictate the “how.” The implementation details and technical approach fall squarely within our domain as technical professionals.&lt;/p&gt;
&lt;p&gt;When a product owner says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We need this feature by next month”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They probably don’t want to say: ignore quality (so &lt;em&gt;“skip writing tests”&lt;/em&gt; or &lt;em&gt;“don’t bother with proper error handling”&lt;/em&gt;). They’re giving you a constraint within which you, as the technical expert, must make appropriate judgments and trade-offs.&lt;/p&gt;
&lt;p&gt;If you genuinely cannot deliver quality work within the given constraints, that’s when you go back to business, not to ask permission to “do it right.” Still, to present business consequences in business terms, they can understand and evaluate. For instance&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“If we proceed as requested, we’ll likely see production outages within three months”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;is something a business stakeholder can assess, or:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We’ll be unable to quickly diagnose issues, resulting in extended downtime that will affect our SLAs”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;speaks to business concerns in a way they can grasp. Especially if we back it up with the previous cases like that.&lt;/p&gt;
&lt;p&gt;Notice this difference from:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We need more time for writing unit tests.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One focuses on the business impact that stakeholders can weigh against other priorities; the other asks them to evaluate a technical practice they’re not equipped to judge.&lt;/p&gt;
&lt;p&gt;Then they can make a decision on changing the scope or delivery time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When we frame our concerns in technical terms, we’re essentially asking non-technical people to make technical decisions, which is precisely what we then complain about.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-role-confusion-problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-role-confusion-problem&quot; aria-label=&quot;the role confusion problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Role Confusion Problem&lt;/h2&gt;
&lt;p&gt;From my experience, when the roles and responsibilities between business and engineering are disturbed, the situation ends badly in the long run. The dysfunction typically follows a predictable pattern.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It begins innocently enough. A developer asks a business stakeholder whether they should add tests or monitoring to a feature.&lt;/strong&gt; Not wanting to delay the timeline, the stakeholder says: “Let’s skip that for now.” The developer complies. The pattern repeats. Soon, the business stakeholder starts believing that making these technical decisions is part of their job. Meanwhile, developers, many of whom are oddly comfortable dodging accountability, keep pushing more technical choices upstairs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Over time, this creates a perfect storm of dysfunction. Business leaders make increasingly complex technical decisions that they fundamentally don’t understand.&lt;/strong&gt; Developers build systems they know are flawed but can conveniently blame on “business requirements.” The system gradually deteriorates under the weight of poor technical decisions. When it all eventually collapses, the finger-pointing begins in earnest. Nobody wins.&lt;/p&gt;
&lt;p&gt;The truth is that this dynamic serves a perverse purpose for both sides. Business gets to feel in control of all aspects of the product, while developers get to avoid the hard work of making tough technical trade-offs under constraint. It’s easier to implement what you’re told, especially when you can blame the outcome on someone else’s decisions.&lt;/p&gt;
&lt;h2 id=&quot;the-responsibility-dodge&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-responsibility-dodge&quot; aria-label=&quot;the responsibility dodge permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Responsibility Dodge&lt;/h2&gt;
&lt;p&gt;The core issue here isn’t really about business overreach. It’s about developers actively looking for ways to avoid responsibility for the systems they build.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Business won’t let me” is the perfect excuse in many ways.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It’s practically unverifiable in the moment. Who’s going to call an emergency meeting to confirm whether the business actually prohibits proper error handling? It conveniently shifts blame upward to people who likely won’t understand the technical implications anyway.&lt;/p&gt;
&lt;p&gt;Moreover, it positions you as the helpless victim rather than the empowered expert you’re supposed to be. It becomes a ready-made get-out-of-jail-free card when the system inevitably falls apart.&lt;/p&gt;
&lt;p&gt;This responsibility avoidance tactic is so common because it works on multiple levels. It prevents you from doing extra work now – implementing proper monitoring or writing thorough tests takes time, after all. It shields you from blame later when problems arise, because you “wanted to do it right”, aye? It requires no confrontation or difficult conversations about trade-offs and priorities. And perhaps most comfortably, it lets you maintain the self-image of a “good developer” who “knows better” but is constrained by clueless managers who just don’t understand quality.&lt;/p&gt;
&lt;p&gt;Here’s the uncomfortable truth: if you build a technical solution you know is flawed, you own that failure. You’re supposed to be the expert. Your job isn’t just to code what you’re told—it’s to find the best technical approach within given constraints. Sometimes that means making difficult trade-offs. Sometimes it means having uncomfortable conversations about what’s truly necessary versus what can be deferred. But it always means taking responsibility for the technical implementation, not passing that responsibility upstairs.&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/on-getting-the-meaningful-discussions/&quot;&gt;On getting the meaningful discussions, and why that’s important&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/tech-debt-doesnt-exist-but-trade/&quot;&gt;Tech Debt doesn’t exist, but trade-offs do&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;finding-the-real-problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#finding-the-real-problem&quot; aria-label=&quot;finding the real problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Finding the Real Problem&lt;/h2&gt;
&lt;p&gt;In my article &lt;a href=&quot;/en/bring_me_problems_not_solutions/&quot;&gt;“Bring me problems, not solutions!”&lt;/a&gt; I discussed how business stakeholders often present us with solutions instead of problems. A classic example is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We need a web app for recording working hours.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;when the actual problem might be:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“HR needs accurate time records to process payroll.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This happens constantly with technical decisions, too, but in reverse—we interpret business requirements as technical directives.&lt;/p&gt;
&lt;p&gt;When someone says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We don’t have time for telemetry!”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They’re not actually talking about telemetry. They probably don’t even know what telemetry is. What they’re saying is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We’re worried about the deadline.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;That’s the real problem – a concern about timely delivery.&lt;/strong&gt; Your job as a technical expert is to recognise this translation error. Once you understand the actual concern—meeting a deadline—you can make informed trade-offs that address both the business need and technical integrity.&lt;/p&gt;
&lt;p&gt;Perhaps you scale back the feature itself rather than the quality of its implementation. Maybe you implement it in phases, with the most critical functionality delivered first. Perhaps you can find ways to streamline other aspects of the work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The point is, you’re making these decisions based on your technical expertise rather than asking business people to make them for you or, worse, assuming they’ve already made them through their requirements.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Read more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/follow_the_money/&quot;&gt;Follow the money to get a better design&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;making-trade-offs-not-excuses&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#making-trade-offs-not-excuses&quot; aria-label=&quot;making trade offs not excuses permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Making Trade-offs, Not Excuses&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;When faced with constraints, we should not throw up our hands and say: “Business won’t let me do it right.” We should analyse requirements and make appropriate trade-offs, using our technical expertise to guide those decisions.&lt;/strong&gt; This is fundamentally different from simply cutting corners or delivering poor quality.&lt;/p&gt;
&lt;p&gt;Consider what’s necessary for system stability and reliability. Some aspects of software development aren’t optional luxuries, they’re essential to a functioning system. Basic error handling, data validation, and security controls aren’t nice-to-haves. They’re fundamental. On the other hand, some features can be simplified while still providing value. Perhaps an advanced search can be trimmed back to basic functionality initially. Maybe a dashboard can display essential metrics first, with additional views later.&lt;/p&gt;
&lt;p&gt;Some work can be deferred without decreasing quality; not all technical improvements need to happen immediately. And often, there’s a minimal viable version that would meet the immediate need while allowing for future enhancement. These are the kinds of nuanced trade-offs that technical experts should make.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This isn’t about cutting corners. It’s about making expert judgments about what’s technically crucial given the constraints.&lt;/strong&gt; Sometimes you’ll need to scale back features, adjust timelines, or implement in phases—but you make these decisions based on your technical expertise, not by asking non-technical stakeholders to make them for you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you’re a senior developer or architect, take a hard look at what decisions you’re punting upstairs that should be yours.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Are you hiding behind “business won’t let me” when you should be saying “I’m responsible for making this work within our constraints”?&lt;/p&gt;
&lt;p&gt;Technical leadership isn’t just about knowing the right technical choice, it’s about having the backbone to stand behind it. It’s about using business requirements and constraints as parameters within which you operate, not excuses for poor quality.&lt;/p&gt;
&lt;p&gt;It’s about making expert technical decisions within those constraints, applying your knowledge and experience to find the best approach possible given the limitations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It means owning those decisions and their consequences, both good and bad.&lt;/strong&gt; When things go well, you share the credit. When they go poorly, you accept responsibility rather than pointing fingers at “business requirements.” It requires communicating regarding business impact, not technical details, so stakeholders can understand the actual consequences of various options without evaluating technical practices themselves.&lt;/p&gt;
&lt;p&gt;And yes, it means having uncomfortable conversations about genuine business risks when necessary.&lt;/p&gt;
&lt;p&gt;Sometimes, despite your best efforts, the constraints don’t allow for a viable solution. In those cases, you need the courage to clearly articulate the business risks in business terms, not as a request for permission to “do things right,” but as vital information for business decision-making.&lt;/p&gt;
&lt;p&gt;As Grace Hopper famously said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“It’s easier to ask forgiveness than it is to get permission.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, own your technical expertise.&lt;/p&gt;
&lt;p&gt;Make decisions with confidence.&lt;/p&gt;
&lt;p&gt;And for everyone’s sake, stop blaming “business” for your technical compromises.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[PostgreSQL JSONB - Powerful Storage for Semi-Structured Data]]></title><description><![CDATA[Object-oriented or relational? You’ve seen this battle in your projects. For many years, we tried to fit the business data, which is usually…]]></description><link>https://event-driven.io/en/postgresql-jsonb-powerful-storage/</link><guid isPermaLink="false">https://event-driven.io/en/postgresql-jsonb-powerful-storage/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ebb805afecbe6fde3e707d3d876481fb/658fc/2025-04-21-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 85%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAADNElEQVR42q2T7U9TVxzH+TemyaYDoUCfaC+l5T506iQ6ZR2TRF/4QjNQYVskYKIxy5Yly94smXO4ScwWg1nUbM4HjNKCMo0Ra0wkkQlsbjMClQ16u61SYIXy2Tm3CRIefOVNvjm5557f535/DyfHjJt0XbtJpL2LjitX6WhfXp2RLjrDXfT3/UI6nWZmZoaFT86Nm1HethtUVRjYNB3n6nIceUK5i+Uu0Mh9yc3Bxo/4Lz3F1NTUYmC4vROfOKjYRFB+QAD9uESwa42KW8g1TyUFOmtWemioP4CZMJmYmFgMbLtwCbdNR/FuwuevxK+F8DrX48zThFPVknMeMH+ll/f3NDEy8oRUKrUYeP7cRRyv+lGUjWyuaWJddQ2qex0VHoOgU2OtUKn4oV2AXfnSoZd3axsZjg0zPr4U8MeLwo1MUcN4Ywevb99DwKgiUGyg2TV0IbmWFapiT6foZYX6GgkcIrUcUKbjWF2GXw2x6Z1G/K9VoxarVPtFs3w6b5XqAqoLxwaOVQp1Ajg4PEgy+ZRMZpbZ2dkFDiVQpF3m38zGnfsIhnYR8Gyw3JXaVHyFsp6aOKeLLnt4r7aBhPkXmen081LOFr58wzbWb99LQH2TtS6DSq9GRYmOO1+zuu7MC7Bz9wFOnr1M69kwZ9quYpoJCyadzgGlPLLbngoCotO+si1i7nThUsVbIBuiUfyKgmZsJXToJJVN31Be+yWh/ccJX79tATOZzDOgx2agFAVxitQdq3xWTWUp7LnSlWo5tIt9rTxE1cET7Pj4NFsPtfLBsTYePY7NcyjGxitA3sKgFSRnrUQ4lWv2XcumKyQnQSnSqa77hA+Pfk+w/hiftvzA40e/PwNeuRy2gl0LbsVSkvC8FSU01O3n9p0owZrPiFyPStRcp3P6+vo59d0ZWr4+zpHDR/ni8+YldeRwM181t3Di21a6b0UZ+XOEn27dJZ74B8maA8bjcYaGBhkY6Kf35156e+8vqQd9D4gKV5GOCN3d3fT03OPhb3/Q82uMewPDPIkns8Dp6WkmJyetiy7v5nKS35PJJGNjcUZHR4nFYoyZJolkitG/n/Lv+GQWyAt+/gfm9JdkA9V7HwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/ebb805afecbe6fde3e707d3d876481fb/507e6/2025-04-21-cover.webp 200w,
/static/ebb805afecbe6fde3e707d3d876481fb/28a80/2025-04-21-cover.webp 400w,
/static/ebb805afecbe6fde3e707d3d876481fb/8d2ea/2025-04-21-cover.webp 800w,
/static/ebb805afecbe6fde3e707d3d876481fb/53334/2025-04-21-cover.webp 1024w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/ebb805afecbe6fde3e707d3d876481fb/36ca5/2025-04-21-cover.png 200w,
/static/ebb805afecbe6fde3e707d3d876481fb/a3397/2025-04-21-cover.png 400w,
/static/ebb805afecbe6fde3e707d3d876481fb/a331c/2025-04-21-cover.png 800w,
/static/ebb805afecbe6fde3e707d3d876481fb/658fc/2025-04-21-cover.png 1024w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/ebb805afecbe6fde3e707d3d876481fb/a331c/2025-04-21-cover.png&quot;
            alt=&quot;2025 04 21 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Object-oriented or relational? You’ve seen this battle in your projects.&lt;/strong&gt; For many years, we tried to fit the business data, which is usually grouped by business use case, into a normalised table structure. Sometimes it fits better, sometimes worse. We learn to cheat it with &lt;a href=&quot;/en/in_the_defence_of_orms/&quot;&gt;Object-Relational Mappers&lt;/a&gt;. They fixed some issues but created others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then &lt;a href=&quot;/en/key-value-stores/&quot;&gt;document databases appeared, trying to look at this perspective from a different angle&lt;/a&gt;.&lt;/strong&gt; They tried to accept that real-world data is fuzzy and usually doesn’t follow the rigid structure, but it’s semi-structured. It’s the classic dilemma: rigid schemas that ensure data integrity but resist change, versus flexible document stores that adapt easily but sacrifice guarantees.&lt;/p&gt;
&lt;p&gt;Still, those databases didn’t give us the consistency guarantees we were comfortable with, like transactions, isolation levels, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL added the JSONB column type to answer the question:&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;“Can we have both?”&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Then other databases like MySQL and SQLite followed.&lt;/p&gt;
&lt;p&gt;Let’s discuss today how it works, and if it’s the actual answer.&lt;/p&gt;
&lt;h2 id=&quot;introduction&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#introduction&quot; aria-label=&quot;introduction permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;First, terminology: the B in JSON stands for Binary&lt;/strong&gt;. PostgreSQL also has a JSON type, but they’re different implementations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JSON stores data as text,&lt;/strong&gt; requiring parsing on each operation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JSONB uses a binary representation&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The binary format of JSONB enables efficient indexing and querying and eliminates the need for repeated parsing.&lt;/strong&gt; The downside is that it takes up more storage space (as it stores not only text but also metadata) and loses the exact formatting and ordering of the original JSON input. That’s usually acceptable, but worth knowing.&lt;/p&gt;
&lt;h2 id=&quot;jsonb-under-the-hood-not-just-json-storage&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#jsonb-under-the-hood-not-just-json-storage&quot; aria-label=&quot;jsonb under the hood not just json storage permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;JSONB Under the Hood: Not Just JSON Storage&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;You can think of a JSONB column as a table inside the column. Each path to property can be seen logically as a different column.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To visualise this conceptual model, imagine if our database could access any path directly without scanning the entire document:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;+----+---------------+--------------------------+--------------------+
| id | customer.name | customer.contact.address | email              |
+----+---------------+--------------------------+--------------------+
| 1  | &quot;John Smith&quot;  | &quot;Portland&quot;               | &quot;john@example.com&quot; |
+----+---------------+--------------------------+--------------------+&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course, that’s a bit of an oversimplification.&lt;/p&gt;
&lt;p&gt;PostgreSQL doesn’t create these virtual columns, but the binary representation effectively allows it to access paths directly without scanning the whole document. This approach provides several benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Path traversal is much faster than parsing text&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fields can be indexed individually&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Query performance is more predictable&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When Postgresql stores a JSONB document, it doesn’t simply dump JSON text into a field. It transforms the document into a binary representation.&lt;/p&gt;
&lt;p&gt;Have a look on the following JSON:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;customer&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;John Smith&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;contact&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;john@example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;phone&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;555-1234&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;address&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Portland&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;country&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;USA&quot;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This hierarchical structure could be flattened into a set of paths for each nested structure:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Path: customer.name = &quot;John Smith&quot;
Path: customer.contact.email = &quot;john@example.com&quot;
Path: customer.contact.phone = &quot;555-1234&quot;
Path: customer.contact.address.city = &quot;Portland&quot;
Path: customer.contact.address.country = &quot;USA&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The goal is: to performantly read a specific field without parsing the entire document.&lt;/strong&gt; How does it achieve it? To understand it, let’s discuss…&lt;/p&gt;
&lt;h2 id=&quot;postgresql-jsonb-implementation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#postgresql-jsonb-implementation&quot; aria-label=&quot;postgresql jsonb implementation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;PostgreSQL JSONB Implementation&lt;/h2&gt;
&lt;h3 id=&quot;storage-internals&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#storage-internals&quot; aria-label=&quot;storage internals permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Storage Internals&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;JSONB’s document is stored as a hierarchical, tree-like structure of key-value pairs, each containing metadata about its type and actual data.&lt;/strong&gt; To enable versioning, it begins with a “magical” single-byte header that identifies the format’s version (currently version 1) and a header with metadata.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When new JSON is stored, it has to be parsed and converted from text to key-value pairs. This conversion happens through a process called “tokenisation&lt;/strong&gt;“.&lt;/p&gt;
&lt;p&gt;For example, using our customer data example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;cust_web_123&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;web_user@example.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;website&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;web_data&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;first_visit&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2023-03-12T15:22:47Z&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;utm_source&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;google_search&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;contact&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;phone&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;+1234567890&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Would be tokenised and stored with internal tree-like structures tracking:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Root: Object {
  Count: 5,
  Children: [
    Key: &quot;id&quot;,
    Value: String &quot;cust_web_123&quot;,

    Key: &quot;email&quot;,
    Value: String &quot;web_user@example.com&quot;,

    Key: &quot;source&quot;,
    Value: String &quot;website&quot;,

    Key: &quot;web_data&quot;,
    Value: Object {
      Count: 2,
      Children: [
        Key: &quot;first_visit&quot;,
        Value: String &quot;2023-03-12T15:22:47Z&quot;,

        Key: &quot;utm_source&quot;,
        Value: String &quot;google_search&quot;
      ]
    },

    Key: &quot;contact&quot;,
    Value: Object {
      Count: 1,
      Children: [
        Key: &quot;phone&quot;,
        Value: String &quot;+1234567890&quot;
      ]
    }
  ]
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Of course, it’s a bit simplified form, but you can think that each array element is actually a different node, and the index in the array is just a nested path.&lt;/strong&gt; Each key and value in a JSONB document is stored with its data type and metadata. For objects and arrays, PostgreSQL maintains counts and offsets to child elements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL’s JSONB tokenisation is sneaky. It not only parses data but also preserves the actual data types of values.&lt;/strong&gt; Each value in the binary format includes a type that identifies whether it’s a string, number, boolean, null, array, or object. Thanks to that PostgreSQL can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ensure that data maintains its semantic meaning,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;enable indexing,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;enable type-specific operations (like numeric comparisons),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;avoid type conversion when not needed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What’s more, it’s not limited to JSON types.&lt;/strong&gt; It distinguishes different numeric types (integers, floating-point) and handles string data with character encoding.&lt;/p&gt;
&lt;p&gt;So, when you search for:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;customer_data-&gt;&apos;customer&apos;-&gt;&apos;contact&apos;-&gt;&apos;address&apos;-&gt;&gt;&apos;city&apos;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;PostgreSQL can navigate directly to that specific token without scanning the entire document. It uses the described hierarchical structure, and gets the exact value with the type.&lt;/p&gt;
&lt;h3 id=&quot;internal-path-representation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#internal-path-representation&quot; aria-label=&quot;internal path representation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Internal Path Representation&lt;/h3&gt;
&lt;p&gt;PostgreSQL uses path extraction operators (&lt;em&gt;-&gt;&lt;/em&gt; and &lt;em&gt;-&gt;&gt;&lt;/em&gt;) for querying the data inside JSON.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When PostgreSQL encounters such query, it:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Parses the path into individual segments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Locates the root object or array in the binary structure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each path segment computes a hash of the key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Uses the hash to look up the corresponding entry in the structure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Navigates to the next level if needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extracts and returns the value in the requested format.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This algorithm is heavily optimised for typical access patterns. For simple path expressions, PostgreSQL can retrieve values with near-constant time complexity, regardless of document size. However, the performance characteristics become more variable for more complex expressions, especially those involving arrays or filtering.&lt;/p&gt;
&lt;p&gt;The algorithm also has specific optimisations for handling arrays differently from objects. Arrays are stored with implicit numeric keys, and PostgreSQL includes specialised operations for array traversal and element access. This becomes important when dealing with large arrays or performing operations like &lt;em&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/functions-json.html&quot;&gt;jsonb_array_elements()&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Understanding this internal path representation explains why some JSONB queries perform better than others.&lt;/strong&gt; Operations that align with PostgreSQL’s internal traversal algorithm (like direct path access to deeply nested values) can be remarkably fast, while operations that require scanning or restructuring (like complex filtering within arrays) might perform less optimally.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read more in the &lt;a href=&quot;https://www.postgresql.org/docs/current/functions-json.html&quot;&gt;documentation about JSON Functions and Operators&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;jsonb-in-practice&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#jsonb-in-practice&quot; aria-label=&quot;jsonb in practice permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;JSONB in practice&lt;/h2&gt;
&lt;p&gt;To see how JSONB actually works, let’s discuss a scenario that manages customer data.&lt;/p&gt;
&lt;p&gt;Here’s how a typical customer model might look in TypeScript:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Customer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  email&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  source&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Different structure depending on acquisition channel&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// At least one of these will be present per customer&lt;/span&gt;
  web_data&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    first_visit&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    utm_source&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    browser&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    pages_viewed&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  mobile_data&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    device&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    os_version&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    app_version&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    notification_token&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  crm_data&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    account_manager&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    company&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    department&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    role&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    meeting_notes&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  contact&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    phone&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    address&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      city&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      country&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      postal_code&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  purchases&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;object&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you tried to model it with relational tables, you’d face a classic data modelling challenge. You’d end up with either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A complex set of tables with joins:&lt;/strong&gt; &lt;em&gt;Customer_Web&lt;/em&gt;, &lt;em&gt;Customer_Mobile&lt;/em&gt;, &lt;em&gt;Customer_CRM&lt;/em&gt; with relationships between them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A wide table with mostly NULL values:&lt;/strong&gt; A single table with columns for every possible field across all sources.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With JSONB, you can implement this in PostgreSQL without forcing every customer profile into the same rigid structure:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; customers &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    id &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    email &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UNIQUE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    customer_data JSONB &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This allows storing profiles with wildly different shapes while keeping common fields queryable. The SQL implementation might look like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INTO&lt;/span&gt; customers &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; customer_data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;VALUES&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;-- Web customer&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;cust_web_123&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;web_user@example.com&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token string&quot;&gt;&apos;{
    &quot;id&quot;: &quot;cust_web_123&quot;,
    &quot;email&quot;: &quot;web_user@example.com&quot;,
    &quot;source&quot;: &quot;website&quot;,
    &quot;name&quot;: &quot;Alex Johnson&quot;,
    &quot;web_data&quot;: {
        &quot;first_visit&quot;: &quot;2023-03-12T15:22:47Z&quot;,
        &quot;utm_source&quot;: &quot;google_search&quot;,
        &quot;browser&quot;: &quot;Chrome 98.0&quot;,
        &quot;pages_viewed&quot;: [&quot;/products&quot;, &quot;/pricing&quot;, &quot;/blog/postgresql-tips&quot;]
    },
    &quot;contact&quot;: {
        &quot;phone&quot;: &quot;+1234567890&quot;,
        &quot;address&quot;: {
            &quot;city&quot;: &quot;Portland&quot;,
            &quot;country&quot;: &quot;USA&quot;,
            &quot;postal_code&quot;: &quot;97201&quot;
        }
    },
    &quot;purchases&quot;: [
        {
            &quot;product_id&quot;: &quot;hosting_basic&quot;,
            &quot;amount&quot;: 9.99,
            &quot;date&quot;: &quot;2023-04-01&quot;,
            &quot;subscription&quot;: true
        }
    ]
}&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Mobile customer with different structure&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;cust_mob_456&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;mobile_user@example.com&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token string&quot;&gt;&apos;{
    &quot;id&quot;: &quot;cust_mob_456&quot;,
    &quot;email&quot;: &quot;mobile_user@example.com&quot;,
    &quot;source&quot;: &quot;ios_app&quot;,
    &quot;mobile_data&quot;: {
        &quot;device&quot;: &quot;iPhone 13&quot;,
        &quot;os_version&quot;: &quot;iOS 16.2&quot;,
        &quot;app_version&quot;: &quot;3.4.1&quot;,
        &quot;notification_token&quot;: &quot;eyJhbGdvcml0aG0iOiJITUFDLVNIQTI1NiIsIml&quot;
    },
    &quot;contact&quot;: {
        &quot;address&quot;: {
            &quot;city&quot;: &quot;Berlin&quot;,
            &quot;country&quot;: &quot;Germany&quot;
        }
    },
    &quot;purchases&quot;: [
        {
            &quot;item_id&quot;: &quot;premium_subscription&quot;,
            &quot;recurring&quot;: true,
            &quot;interval&quot;: &quot;monthly&quot;,
            &quot;price&quot;: 14.99,
            &quot;currency&quot;: &quot;EUR&quot;,
            &quot;payment_method&quot;: &quot;apple_pay&quot;
        },
        {
            &quot;item_id&quot;: &quot;in_app_coins&quot;,
            &quot;package&quot;: &quot;large&quot;,
            &quot;quantity&quot;: 5000,
            &quot;price&quot;: 49.99,
            &quot;currency&quot;: &quot;EUR&quot;,
            &quot;payment_method&quot;: &quot;apple_pay&quot;
        }
    ]
}&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Despite the varying structures, you can still query across all customer types:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
    id&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;source&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; source&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;web_data&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;first_visit&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;mobile_data&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;app_version&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;crm_data&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;account_manager&apos;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; source_specific_data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;contact&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;address&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;city&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; city&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    jsonb_array_length&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;purchases&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; purchase_count

&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; customers
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;contact&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;address&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;country&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;USA&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Le’ts have a look first on_WHERE (customer_data-&gt;‘contact’-&gt;‘address’-&gt;&gt;‘country’) = ‘USA’_ statement.&lt;/p&gt;
&lt;p&gt;When getting such WHERE query, PostgreSQL does following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Computes the path &lt;em&gt;contact.address.country&lt;/em&gt; for each document&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extracts the string value at that path&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compares it to the literal &lt;em&gt;‘USA’&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With a traditional JSON text column, this would require parsing the entire document. With JSONB, PostgreSQL can navigate directly to the relevant portion of the binary structure.&lt;/p&gt;
&lt;p&gt;The data in the SELECT statement is processed similarly.&lt;/p&gt;
&lt;p&gt;PostgreSQL also has syntactic sugar called JSON_TABLE see more in the &lt;a href=&quot;https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-SQLJSON-TABLE&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;indexing-and-performance-optimisation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#indexing-and-performance-optimisation&quot; aria-label=&quot;indexing and performance optimisation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Indexing and Performance Optimisation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Thanks to the binary, hierarchical structure we just described, PostgreSQL can index JSONB data.&lt;/strong&gt; It also offers several index types, each with distinct use cases. It’s worth knowing their pros and cons.&lt;/p&gt;
&lt;h3 id=&quot;gin-indexes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#gin-indexes&quot; aria-label=&quot;gin indexes permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;GIN Indexes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/gin.html&quot;&gt;The GIN (Generalized Inverted Index)&lt;/a&gt; is PostgreSQL’s primary weapon for speeding up JSONB queries.&lt;/strong&gt; A GIN index creates index entries for each key and value in your JSONB documents, making it particularly effective for containment checks and path existence.&lt;/p&gt;
&lt;p&gt;Creating a GIN index is straightforward:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; idx_customer_data &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; customers &lt;span class=&quot;token keyword&quot;&gt;USING&lt;/span&gt; GIN &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer_data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;When PostgreSQL builds this index, it:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Iterates through every JSONB document in the table&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Extracts each key and value pair at every level of nesting&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Creates index entries for each key and for each scalar value&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Organises these entries in a tree structure optimised for lookups&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The resulting index is usefull for certain operations like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Find all customers with specific tags (contains operator)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; customers &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; customer_data @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{&quot;tags&quot;: [&quot;premium&quot;]}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Find all customers with a specific field (existence operator)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; customers &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; customer_data ? &lt;span class=&quot;token string&quot;&gt;&apos;promotion_code&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Find all customers with any of these fields (any existence operator)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; customers &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; customer_data ?&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; array&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;discount&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;coupon&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;promotion_code&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These queries can use the GIN index directly without extracting paths, making them highly efficient even with large datasets. The execution plan would show something like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Bitmap Heap Scan on customers  (cost=4.26..8.27 rows=1 width=32)
  Recheck Cond: (customer_data @&gt; &apos;{&quot;tags&quot;: [&quot;premium&quot;]}&apos;::jsonb)
  -&gt;  Bitmap Index Scan on idx_customer_data  (cost=0.00..4.26 rows=1 width=0)
        Index Cond: (customer_data @&gt; &apos;{&quot;tags&quot;: [&quot;premium&quot;]}&apos;::jsonb)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;However, GIN indexes have significant drawbacks:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;They can be large, sometimes larger than the table itself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They can slow down write operations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They require more maintenance (vacuum).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They don’t help with range queries or sorting.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For each key or scalar value, the GIN index maintains a posting list of row IDs where that element appears. These posting lists are compressed and optimised for fast intersection operations, which is why containment checks are so efficient.&lt;/p&gt;
&lt;p&gt;When PostgreSQL executes a containment query like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;customer_data @&gt; &apos;{&quot;status&quot;: &quot;active&quot;, &quot;type&quot;: &quot;business&quot;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Looks up the posting list for the key “status” with value “active”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Looks up the posting list for the key “type” with value “business”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Computes the intersection of these posting lists&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Returns the matching row IDs&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This set-based operation is highly efficient compared to extracting and comparing values from each document.&lt;/p&gt;
&lt;h3 id=&quot;b-tree-indexes-for-extracted-values&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#b-tree-indexes-for-extracted-values&quot; aria-label=&quot;b tree indexes for extracted values permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;B-Tree Indexes for Extracted Values&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;For simpler cases where you frequently filter on a specific JSON path, creating a &lt;a href=&quot;https://www.postgresql.org/docs/current/btree.html&quot;&gt;B-Tree index&lt;/a&gt; on an extracted value is often more efficient:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Index for a specific extracted field&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; idx_customer_source &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; customers &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;source&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This creates a traditional B-Tree index on the extracted string value. Internally, PostgreSQL treats this as an expression index - it computes the expression &lt;em&gt;(customer_data-&gt;&gt;‘source’)&lt;/em&gt; for each row and indexes the result.&lt;/p&gt;
&lt;p&gt;The execution plan for a query using this index would show:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Index Scan using idx_customer_source on customers  (cost=0.28..8.29 rows=1 width=32)
  Index Cond: ((customer_data-&gt;&gt;&apos;source&apos;::text) = &apos;website&apos;::text)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These indexes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Are much smaller than GIN indexes,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Have less impact on write performance,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Support range queries and sorting,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Only help with queries that exactly match the indexed expression.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What’s more, you can also use it to set up unique constraints and get the same strong checks as for regular tables!&lt;/p&gt;
&lt;p&gt;Understanding the internal mechanisms helps explain why you might want different index types for different query patterns.&lt;/p&gt;
&lt;h3 id=&quot;expression-and-partial-indexes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#expression-and-partial-indexes&quot; aria-label=&quot;expression and partial indexes permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Expression and Partial Indexes&lt;/h3&gt;
&lt;p&gt;For more specific query patterns, you can combine these approaches:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Location-based index for frequently filtered fields&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; idx_customer_country &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; customers &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;contact&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;address&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;country&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Specialized conditional index for specific customer types&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; idx_web_utm &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; customers &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;web_data&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;utm_source&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;source&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;website&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The partial index only indexes rows where the condition &lt;em&gt;customer_data-&gt;&gt;‘source’ = ‘website’&lt;/em&gt; is true. This makes the index much smaller while still accelerating queries for web customers:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Query that can use the partial index&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; customers
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;source&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;website&apos;&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;web_data&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;utm_source&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;google&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Internally, PostgreSQL maintains separate metadata about which index portions apply to which queries. The query planner uses statistics about data distribution to decide whether to use an index or not, which explains why sometimes PostgreSQL might choose a sequential scan even when an index exists.&lt;/p&gt;
&lt;p&gt;Suppose we have a table with 1 million customer records, and we frequently run queries to find customers from specific countries:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Without an index, this requires scanning all records&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; customers
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;contact&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;address&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;country&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Germany&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The execution plan would show:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Seq Scan on customers  (cost=0.00..24053.00 rows=10000 width=32)
  Filter: ((customer_data-&gt;&apos;contact&apos;-&gt;&apos;address&apos;-&gt;&gt;&apos;country&apos;::text) = &apos;Germany&apos;::text)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After adding an appropriate index:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;INDEX&lt;/span&gt; idx_country &lt;span class=&quot;token keyword&quot;&gt;ON&lt;/span&gt; customers &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;contact&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;address&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;country&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The execution plan becomes:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Index Scan using idx_country on customers  (cost=0.42..341.50 rows=10000 width=32)
  Index Cond: ((customer_data-&gt;&apos;contact&apos;-&gt;&apos;address&apos;-&gt;&gt;&apos;country&apos;::text) = &apos;Germany&apos;::text)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This can reduce query time from seconds to milliseconds. But the choice between GIN and B-Tree indexes isn’t always obvious. If we frequently need to find customers with specific combinations of attributes, a GIN index might be better:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Query that benefits from a GIN index&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; customers
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; customer_data @&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{&quot;contact&quot;: {&quot;address&quot;: {&quot;country&quot;: &quot;Germany&quot;}}, &quot;status&quot;: &quot;active&quot;}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Performance is good enough for most typical cases, and if you don’t have a huge amount of data, the performance of JSONB with indexing is similar to traditional columns.&lt;/p&gt;
&lt;p&gt;Still, if it’s worth knowing that JSONB:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Can perform worse for simple single-value lookups: Traditional columns with B-Tree indexes typically outperform JSONB path extraction,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;can perform better for retrieving complete complex objects. Avoiding joins can lead to significant performance advantages,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Has variable performance for analytical queries: Depending on indexing strategy, can be either much faster or much slower than normalised data&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;While JSONB offers flexibility, it has performance characteristics you should understand, especially for large documents&lt;/strong&gt;. PostgreSQL applies &lt;a href=&quot;https://www.postgresql.org/docs/current/storage-toast.html&quot;&gt;TOAST (The Oversized-Attribute Storage Technique)&lt;/a&gt; compression to JSONB documents exceeding 2KB, storing them outside the main table in a TOAST table. This introduces retrieval overhead, as accessing TOASTed data requires additional I/O operations and CPU time for decompression.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A common anti-pattern is storing large arrays or deeply nested structures in a single JSONB document.&lt;/strong&gt; For example, storing an entire order history with hundreds of orders in a customer document might seem convenient, but it leads to document bloat, poor query performance, and contention issues. The same applies to storing the whole contents of text documents.&lt;/p&gt;
&lt;p&gt;Read more in &lt;a href=&quot;https://pganalyze.com/blog/5mins-postgres-jsonb-toast&quot;&gt;Postgres performance cliffs with large JSONB values and TOAST&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;other-jsonb-features-and-patterns&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#other-jsonb-features-and-patterns&quot; aria-label=&quot;other jsonb features and patterns permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Other JSONB Features and Patterns&lt;/h2&gt;
&lt;h3 id=&quot;unique-constraints&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#unique-constraints&quot; aria-label=&quot;unique constraints permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Unique Constraints&lt;/h3&gt;
&lt;p&gt;You don’t have to abandon all data validation when using JSONB. PostgreSQL allows you to enforce constraints on JSONB documents:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Ensuring required fields regardless of customer source&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; customers &lt;span class=&quot;token keyword&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CONSTRAINT&lt;/span&gt; valid_customer
&lt;span class=&quot;token keyword&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    customer_data ? &lt;span class=&quot;token string&quot;&gt;&apos;id&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;
    customer_data ? &lt;span class=&quot;token string&quot;&gt;&apos;email&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;
    customer_data ? &lt;span class=&quot;token string&quot;&gt;&apos;source&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt;
    customer_data ? &lt;span class=&quot;token string&quot;&gt;&apos;contact&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Source-specific validation&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ALTER&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; customers &lt;span class=&quot;token keyword&quot;&gt;ADD&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CONSTRAINT&lt;/span&gt; valid_web_customer
&lt;span class=&quot;token keyword&quot;&gt;CHECK&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;source&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;website&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;OR&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer_data ? &lt;span class=&quot;token string&quot;&gt;&apos;web_data&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;AND&lt;/span&gt; customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;web_data&apos;&lt;/span&gt; ? &lt;span class=&quot;token string&quot;&gt;&apos;first_visit&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These constraints leverage PostgreSQL’s ability to check path existence and values within the binary representation. Internally, PostgreSQL evaluates these expressions during insert and update operations, using the same path traversal algorithms that power queries.&lt;/p&gt;
&lt;h3 id=&quot;json-path-queries&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#json-path-queries&quot; aria-label=&quot;json path queries permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;JSON Path Queries&lt;/h3&gt;
&lt;p&gt;PostgreSQL 12+ introduces a powerful new way to work with JSONB through the SQL/JSON path language. This provides a more expressive syntax for complex data extraction:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; jsonb_path_query&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    customer_data&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;$.purchases[*] ? (@.amount &gt; 10 &amp;amp;&amp;amp; @.subscription == true)&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; customers&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This query finds all purchases that are subscriptions with an amount greater than 10. The path expression is evaluated against each document, and matching elements are returned.&lt;/p&gt;
&lt;p&gt;Internally, PostgreSQL parses the path expression into an execution plan specific to JSON path traversal.&lt;/p&gt;
&lt;h3 id=&quot;aggregation-for-heterogeneous-data&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#aggregation-for-heterogeneous-data&quot; aria-label=&quot;aggregation for heterogeneous data permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Aggregation for Heterogeneous Data&lt;/h3&gt;
&lt;p&gt;JSONB works well with PostgreSQL’s aggregation functions:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- Find average purchase amount across all customers&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;AVG&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;jsonb_array_elements&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;purchases&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;amount&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;::&lt;span class=&quot;token keyword&quot;&gt;numeric&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; customers&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;-- Group purchases by product type&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;product_id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;item_id&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; product&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;COUNT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt;
    customers&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    jsonb_array_elements&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer_data&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;purchases&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; p
&lt;span class=&quot;token keyword&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;BY&lt;/span&gt;
    product&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;em&gt;jsonb_array_elements&lt;/em&gt; internally unwraps the binary representation of the array, extracting each element as a separate JSONB value. This enables the array to handle optimisations in binary format, making it efficient even for large arrays.&lt;/p&gt;
&lt;h3 id=&quot;hybrid-relational-document-models&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#hybrid-relational-document-models&quot; aria-label=&quot;hybrid relational document models permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Hybrid Relational-Document Models&lt;/h3&gt;
&lt;p&gt;Of course, you can mix traditional columns with JSONB ones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For those columns that you know will always exist, or are the same, you can set up regular tables, and those with weaker schema, you can use JSONB.&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; customers &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    id &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    email &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UNIQUE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    created_at &lt;span class=&quot;token keyword&quot;&gt;TIMESTAMP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NOW&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    source &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;-- Common fields as columns&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;-- Variable parts as JSONB&lt;/span&gt;
    source_data JSONB &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    contact_info JSONB &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    purchases JSONB &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This hybrid approach gives you the best of both worlds: schema enforcement where it matters and flexibility where needed. It’s often the most practical approach in real-world systems.&lt;/p&gt;
&lt;p&gt;The hybrid aligns with PostgreSQL’s internal implementation. Traditional columns can use the highly optimised columnar storage and indexing mechanisms PostgreSQL has refined over decades, while JSONB columns leverage the specialised binary representation for flexible data.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;JSONB represents a practical compromise between rigid relational schemas and completely schemaless document databases.&lt;/strong&gt; It provides an excellent balance of flexibility and performance for most transactional data operations, and its filtering capabilities are sufficient for most business operations.&lt;/p&gt;
&lt;p&gt;Of course, in scenarios requiring absolute top performance, like business analytics with complex filtering, data warehousing, or very high-throughput OLTP systems, traditional columns with specialised indexes often perform better. In practice, a hybrid approach works best: use traditional columns for fixed, frequently queried attributes and JSONB for variable parts of your data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Big strength of JSONB is handling schema evolution.&lt;/strong&gt; In traditional relational databases, schema changes often require adding columns with potential downtime, updating application code, and migrating existing data. These operations introduce risk and complexity, especially in production environments.&lt;/p&gt;
&lt;p&gt;With JSONB, you can evolve your schema more gracefully. When business requirements change and you need to store additional data, you don’t need to modify the database schema. Your application can simply start including new fields in the JSON documents it writes, and older documents without those fields continue to work fine.&lt;/p&gt;
&lt;p&gt;I’m biased, but I extremely like JSONB and have used it in the past in my system. It can speed up development for line-of-business applications. Syntax can be a bit tricky, but once you learn it, or provide some wrapper it works like a charm.&lt;/p&gt;
&lt;p&gt;That’s alos why I created Pongo. It’s a JS/TS library that uses a Mongo-like API but uses Postgres underneath, leveraging JSONB data type to get strong consistency benefits. See: &lt;a href=&quot;https://github.com/event-driven-io/pongo&quot;&gt;https://github.com/event-driven-io/pongo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pongo treats PostgreSQL as a Document Database, benefiting from JSONB support. Unlike the plain text storage of the traditional JSON type, JSONB stores JSON data in a binary format. This simple change brings significant advantages in terms of performance and storage efficiency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See also my video where I explained internals of both Pongo and JSONB:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/p-6fpV_GDEs?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/postgres_superpowers/&quot;&gt;PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/strategy_on_migrating_relational_data_to_document_based/&quot;&gt;General strategy for migrating relational data to document-based&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/key-value-stores/&quot;&gt;Unobvious things you need to know about key-value stores&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/sql_support_in_pongo/&quot;&gt;Running a regular SQL on Pongo documents&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://martendb.io/&quot;&gt;Marten - .NET Transactional Document DB and Event Store on PostgreSQL&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Monolith-First - are you sure?]]></title><description><![CDATA[Welcome to the new week! Our industry is highly related to fashion and trends. Trends set up by the tech influencers promoting some ideas…]]></description><link>https://event-driven.io/en/monolith-first-are-you-sure/</link><guid isPermaLink="false">https://event-driven.io/en/monolith-first-are-you-sure/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our industry is highly related to fashion and trends.&lt;/strong&gt; Trends set up by the tech influencers promoting some ideas, quite often that they haven’t even tried to apply in the real system. We’re the guinypigs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every few years, our industry rallies around a new architectural approach that promises to solve all our problems.&lt;/strong&gt; A few years ago, we all wanted to be micro and distributed. Microservices for the win!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lately, the pendulum swung, and we hear the&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;“modular monolith first!”&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/10cfda5dbecf0a10f5583a9f8f5b2290/16038/2025-04-14-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAUCA//EABUBAQEAAAAAAAAAAAAAAAAAAAMC/9oADAMBAAIQAxAAAAHhuiOpioQ//8QAGRAAAgMBAAAAAAAAAAAAAAAAAAEDEBMS/9oACAEBAAEFAlCZpmLrlV//xAAZEQEAAgMAAAAAAAAAAAAAAAABAAMRE1H/2gAIAQMBAT8BKwMTQdn/xAAaEQACAgMAAAAAAAAAAAAAAAAAAQIDERRR/9oACAECAQE/AZXSbybD4f/EABgQAAIDAAAAAAAAAAAAAAAAABAhAAEx/9oACAEBAAY/AnFRwf/EABsQAQACAgMAAAAAAAAAAAAAAAEAESFhEDFx/9oACAEBAAE/IUNZSrJ7NUoTMB6Bx//aAAwDAQACAAMAAAAQgC//xAAYEQADAQEAAAAAAAAAAAAAAAAAARFBYf/aAAgBAwEBPxB9ZW6dB//EABgRAAMBAQAAAAAAAAAAAAAAAAABEUFh/9oACAECAQE/EENHJhzH/8QAGhABAAIDAQAAAAAAAAAAAAAAAQARECFRcf/aAAgBAQABPxBCxUrRuUtjWABOT2A0VuUT/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/10cfda5dbecf0a10f5583a9f8f5b2290/507e6/2025-04-14-cover.webp 200w,
/static/10cfda5dbecf0a10f5583a9f8f5b2290/28a80/2025-04-14-cover.webp 400w,
/static/10cfda5dbecf0a10f5583a9f8f5b2290/8d2ea/2025-04-14-cover.webp 800w,
/static/10cfda5dbecf0a10f5583a9f8f5b2290/e00f7/2025-04-14-cover.webp 1000w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/10cfda5dbecf0a10f5583a9f8f5b2290/37402/2025-04-14-cover.jpg 200w,
/static/10cfda5dbecf0a10f5583a9f8f5b2290/4cda9/2025-04-14-cover.jpg 400w,
/static/10cfda5dbecf0a10f5583a9f8f5b2290/c60e9/2025-04-14-cover.jpg 800w,
/static/10cfda5dbecf0a10f5583a9f8f5b2290/16038/2025-04-14-cover.jpg 1000w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/10cfda5dbecf0a10f5583a9f8f5b2290/c60e9/2025-04-14-cover.jpg&quot;
            alt=&quot;Al Naslaa: meet Earth&amp;#39;s most bizarre geological rock formation - Big Think&quot;
            title=&quot;Al Naslaa: meet Earth&amp;#39;s most bizarre geological rock formation - Big Think&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Or: &lt;em&gt;“If you cannot deal with monolith, microservices won’t work for you!”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I wrote an article some time ago about &lt;a href=&quot;/en/so-you-want-to-break-down-monolith/&quot;&gt;the hardship of breaking down monoliths into microservices&lt;/a&gt; and asked whether that makes sense in most cases. That could look like following the monolith-first gold advice, but actually it was not.&lt;/p&gt;
&lt;p&gt;It sounds like our industry is filled with Siths, as we’re dealing with absolute blank statements. They’re nice hot takes to tell on social media, promote on Youtube or use in an online course, but the reality is much more complex.&lt;/p&gt;
&lt;p&gt;On paper, it sounds perfect: start with a single codebase divided into clear modules, enjoy the simplicity of a monolith, and preserve the option to split into microservices later. But…&lt;/p&gt;
&lt;h2 id=&quot;simple-vs-simplistic&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#simple-vs-simplistic&quot; aria-label=&quot;simple vs simplistic permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Simple vs Simplistic&lt;/h2&gt;
&lt;p&gt;Let’s acknowledge the appeal first. You get a single deployment pipeline, avoiding the distributed systems’ complexity tax. Your developers can navigate the entire codebase without jumping between repositories. Cross-cutting concerns have a single known location. Communication between modules happens through direct method calls rather than over a network.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Many systems don’t need distribution from day one.&lt;/strong&gt; A well-modularized monolith should be easier to achieve than a constellation of small services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The simplicity sounds real but can be deceptive.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Of course, officially, we want to be good citizens, invest in modular design, and avoid chattiness and complexity, but too often, instead of a simple solution, we’re getting a simplistic solution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Essentially, we’re lying to ourselves, as the actual reason is that we do it because we want to take shortcuts.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We want to massage the boundaries with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;transactions spanning across multiple features or modules,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;shared database to query other modules,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;change multiple modules at once,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;don’t care about data governance,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our “modular monolith” is as modular as typical micro-services are “micro”.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I agree that our solutions should be simple, but they should not be simplistic.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-boundary-problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-boundary-problem&quot; aria-label=&quot;the boundary problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Boundary Problem&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;It’s kinda sad that we need to use the “modular” adjective in the “modular monolith”.&lt;/strong&gt; It’s like claiming that a monolith, by definition, is not modular. Well, actually, it should be. If it’s not, then it’s not a monolith but just a &lt;a href=&quot;http://www.laputan.org/mud/&quot;&gt;big ball of mud&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Still, this issue with the name highlights the main challenge. In theory, a modular monolith has clear boundaries between its components. In practice, these boundaries erode fast.&lt;/p&gt;
&lt;p&gt;Consider a typical e-commerce platform with “Orders,” “Products,” and “Customers” modules. Without physical separation, developers inevitably create shortcuts:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ProcessOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Order&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Direct access to another module&apos;s repository&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; customer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;customerRepository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;GetById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CustomerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Logic that belongs in the Customer module&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;customer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;LoyaltyPoints &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ApplyDiscount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0.1m&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;This happens not because developers are careless but because it’s the path of least resistance.&lt;/strong&gt; When working on a feature spanning modules, it’s easier to add a direct dependency than to define interfaces and maintain module autonomy properly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The boundary violations compound exponentially.&lt;/strong&gt; Each small violation creates a precedent for more violations. The pattern I’ve seen repeatedly is that modules start with clean boundaries, but as feature development accelerates, these boundaries become increasingly porous. What makes this particularly insidious is that it happens in small increments. A tired developer working too long on a feature adds what seems like a harmless dependency. No alarms go off. Six months later, you’ve got a monolith that’s merely a traditional monolith with extra folders.&lt;/p&gt;
&lt;p&gt;Of course, we can try to refactor, and instead of using the repository with direct access to the storage, we can have a “well defined public API” and use it as hexagonal architecture, e.g.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;csharp&quot;&gt;&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code class=&quot;language-csharp&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token return-type class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;ProcessOrder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;ICustomerService&lt;/span&gt; customerService&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Order&lt;/span&gt; order&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt;&lt;/span&gt; discount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
        customerService&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CalculateDiscountFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CustomerId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;TotalAmount
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;discount &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        order&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ApplyDiscount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;discount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But well, it is actually Potato vs Potahto.&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/Mc3Fn7R9mkE?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;The problem still exists; it’s a bit reduced, as the other team can have a bit more control over it and can set up some anti-corruption layer, but the boundaries are still mixed.&lt;/p&gt;
&lt;h2 id=&quot;release-train&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#release-train&quot; aria-label=&quot;release train permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Release Train&lt;/h2&gt;
&lt;p&gt;The typical advice is to use &lt;a href=&quot;https://en.wikipedia.org/wiki/Monorepo&quot;&gt;mono-repo&lt;/a&gt;, keeping all modules in the same repo. That should streamline deployment and make it easier to do cross-cutting changes. That sounds great on paper, but… Choo-choo, here comes the release train!&lt;/p&gt;
&lt;p&gt;If we have a monolithic deployment, then we need to deploy all modules at the same time. So if one of our friends from the other team merged something in their module, then if we want to deploy our stuff, then we need to deploy everything, including their changes.&lt;/p&gt;
&lt;p&gt;I had numerous cases where the other team claimed that they tested everything, that it works, and that it won’t break anything, but then after deployment, it somehow didn’t work. Maybe migration failed, maybe some dependency clashed, maybe other random thing. Then it’s evening, you just wanted to deploy a small thing, and you’re trying to patch other teams’ changes.&lt;/p&gt;
&lt;p&gt;Of course, you can and should do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;feature toggles,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;avoid breaking changes,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;version your changes, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But well, are those monolith-first-influencers telling you about that? Read also in &lt;a href=&quot;/en/dont-oversell-ideas-trunk-based-development/&quot;&gt;Don’t Oversell Ideas: Trunk-Based Development Edition&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Of course, the mature solution is to pack each module into the deployment package (NuGet, Maven, Npm, etc.). Then, each package is released independently. Having that, even if the latest code is updated in the repo, you don’t need to bump the dependency and release with the old version.&lt;/p&gt;
&lt;p&gt;But then we’re falling into another issue, which is that we need to have a process for aligning dependencies and knowing what version of each module is actually deployed. And that’s how we’re coming to…&lt;/p&gt;
&lt;h2 id=&quot;tooling-immaturity&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tooling-immaturity&quot; aria-label=&quot;tooling immaturity permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Tooling (im)maturity&lt;/h2&gt;
&lt;p&gt;One of the advantages showed for modular monolith is that it makes easier deployments and CI/CD process. You don’t need to use Kubernetes, Argo, and all that fancy stuff. But don’t you?&lt;/p&gt;
&lt;p&gt;Unfortunately, managing mono-repos is not a common practice in most developed environments. You’re left with configuring pipelines manually and handle packages version and deployments.&lt;/p&gt;
&lt;p&gt;Of course, there are tools like &lt;a href=&quot;https://nx.dev/&quot;&gt;Nx&lt;/a&gt;, &lt;a href=&quot;https://bazel.build/&quot;&gt;Bazel&lt;/a&gt;, &lt;a href=&quot;https://turbo.build/&quot;&gt;TurboRepo&lt;/a&gt; that makes it easier, but frankly even though they can build various development technologies, they’re more optimised for frontend as that’s where they started. Also knowledge about them in environments like .NET, Java, so those leaning towards monoliths are at best low.&lt;/p&gt;
&lt;p&gt;Of course there are approaches like &lt;a href=&quot;https://spring.io/projects/spring-modulith&quot;&gt;Spring Modulith&lt;/a&gt; or &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview&quot;&gt;.NET Aspire&lt;/a&gt; that trying to make that easier, but then, at least with &lt;a href=&quot;/en/nay_to_aspire/&quot;&gt;Aspire it also ends up with simplistic instead of simple solution&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So, yes, you can have a mature and nicely defined CI/CD process, but it doesn’t come without hassle as it’s advocated; it needs to be well-planned and iterated, as the tooling is just not yet there.&lt;/p&gt;
&lt;p&gt;Also, if you look at environments like C# or JavaScript, they weren’t defined for such a modular approach.You cannot restrict easily access and referencing other modules, as C# can only do that for assembly, JavaScript for ES module.&lt;/p&gt;
&lt;p&gt;You can trick that with static analysis or architecture tests, but again, those are tricks and not stuff that you can get out of the box.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don’t get me wrong; if you’re going with distributed systems like micro-services, you’ll also need to think about it.&lt;/strong&gt; The CNCF-related stack can indeed be overcomplicated, but it can also be more mature, as it was built to support those specific scenarios. If you know what you’re doing, you can develop the simpler stack for the monolith, but it’ll still require work and thinking. If you don’t, then you can come up with a more fragile and less organised flow that will require constant tweaking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s just not as simple as it’s pictured.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-resource-contention-reality&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-resource-contention-reality&quot; aria-label=&quot;the resource contention reality permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Resource Contention Reality&lt;/h2&gt;
&lt;p&gt;In a microservices architecture, if the “Product Catalog” service has a memory leak, it doesn’t affect order processing. If the “Payment” service &lt;a href=&quot;https://www.architecture-weekly.com/i/159744317/memory-bound-vs-cpu-bound&quot;&gt;is CPU-bound&lt;/a&gt;, it doesn’t slow down inventory updates. This isolation is a fundamental advantage of distribution.&lt;/p&gt;
&lt;p&gt;The problems with resource contention in modular monoliths are very real:&lt;/p&gt;
&lt;p&gt;Memory leaks in one module bring down the entire system, requiring a full restart that impacts all business functions.&lt;/p&gt;
&lt;p&gt;CPU-intensive calculations in one module make interactive user operations sluggish across the entire application.&lt;/p&gt;
&lt;p&gt;A module with improper exception handling can crash the entire application.&lt;/p&gt;
&lt;p&gt;One module upgrading a shared library can break other modules that aren’t ready for the change.&lt;/p&gt;
&lt;p&gt;These issues aren’t theoretical—they’re daily operational realities for teams with modular monoliths in production.&lt;/p&gt;
&lt;h2 id=&quot;the-database-coupling-nightmare&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-database-coupling-nightmare&quot; aria-label=&quot;the database coupling nightmare permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Database Coupling Nightmare&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;While we often focus on code-level coupling, the shared database in a modular monolith presents the most intractable problem.&lt;/strong&gt; In a true microservices architecture, each service owns its data.&lt;/p&gt;
&lt;p&gt;You can also have the same separation per database or schema in a modular monolith. Still, most of the teams are selecting separation module-per-schema. Why? Quite often, because you can spin up transaction across multiple schemas and query data from multiple schemas in the same connections. Cheating, remember?&lt;/p&gt;
&lt;p&gt;A shared database creates invisible dependencies between modules. Consider what happens when the “Orders” module needs to change its schema to support a new feature. Since the “Analytics” module directly queries the Orders tables, you now have a cross-module dependency that isn’t visible in your code but will break spectacularly at runtime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m sure that if you tried to distribute a portion of your monolith, taking out some module, you found multiple times such hidden dependencies.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Of course, you can set up the permission rules for schemas and ensure that other modules can’t access your data. But well, again that’s something that you need to plan and it doesn’t happen out of the box.&lt;/p&gt;
&lt;h2 id=&quot;the-generic-unit-of-work-trap&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-generic-unit-of-work-trap&quot; aria-label=&quot;the generic unit of work trap permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Generic Unit of Work Trap&lt;/h2&gt;
&lt;p&gt;And here comes my favourite nightmare: the generic unit of work. Martin Fowler defines it as everything one does during a business transaction that can affect the database. When the unit of work is finished, it will provide everything that needs to be done to change the database as a result of the work.&lt;/p&gt;
&lt;p&gt;In modular monolith, even if we cheat, we want to try to decouple our flow into smaller pieces. That can mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Handling different business operations as different commands,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;publishing the events to trigger the next step of the workflow,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most people using monolith are &lt;em&gt;afraid&lt;/em&gt; of eventual consistency. They want to process all at once in the same process, most often also in the same transaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As they decoupled pieces logically, they wanted to use a generic mechanism to gather all the changes in the same request.&lt;/strong&gt; As the mechanism is generic, we need to expect everything and prepare the implementation for everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It usually starts simply by opening transactions across the whole request,&lt;/strong&gt; but that fails as we have more complex flows and start hitting deadlocks and performance issues (as multiple transactions try to modify the same data).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then we optimise it, opening transactions only upon save.&lt;/strong&gt; Before that, we’re caching our changes in a unit of work changeset, but then the challenge appears, as this data is not stored physically in the database; then, if we try to query it, it won’t be there. Why do we need to query? E.g. to access data stored in command processing in the next step of the workflow triggered by the event.&lt;/p&gt;
&lt;p&gt;So we add a 2nd level cache and either load data by ID from the cache or from the DB (if it’s not in the cache). But then we’re hitting discrepancies and are not always able to detect what’s the actual source of truth. Concurrency issues are killing us. Ah, and at some point reading by id is not enough, we need to do more advanced filtering. Try to merge 2nd level cache with data from db…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I did all of that in past, and frankly, I hate every minute spent working on such a mechanism. It all looks simple when you’re starting, but it’s a long tail of accidental complexity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Of course, there are solutions. You can set up a rule that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;you storing results, and even handling multiple commands for user facing, synchronous operations (Read more in &lt;a href=&quot;/en/simple_transactional_command_orchestration/&quot;&gt;How to handle multiple commands in the same transaction&lt;/a&gt;),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each of the messages that are published as an outcome needs to be published through an &lt;a href=&quot;/en/outbox_inbox_patterns_and_delivery_guarantees_explained/&quot;&gt;Outbox Pattern&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That gives you a proper process isolation, delivery guarantee, simpler to reason flow, but can bring eventual consistency for multiple step workflows. That’s most of the time acceptable by business, can be tricked to be synchronous with &lt;a href=&quot;/en/long_polling_and_eventual_consistency/&quot;&gt;long polling&lt;/a&gt; but it’s a tradeoff, and something to consider. Plus &lt;a href=&quot;/en/ordering_in_postgres_outbox/&quot;&gt;implementing outbox on your own doesn’t have to be so simple as it seems&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;monolith-first-modular-first&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#monolith-first-modular-first&quot; aria-label=&quot;monolith first modular first permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Monolith-first? Modular-first!&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I’m not against monolith-first. Moreover, I agree with the general principles, but I wanted to debunk some myths around it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The modular monolith isn’t a free lunch.&lt;/strong&gt; Like all architectural approaches, it comes with tradeoffs. The problem isn’t that these tradeoffs exist but that they’re rarely acknowledged by proponents of the “modular monolith first” advice.&lt;/p&gt;
&lt;p&gt;If you’re a technical leader making architectural decisions, don’t fall for the myth that a modular monolith gives you the best of both worlds without the drawbacks of either. Instead, carefully consider your team’s capabilities, business requirements, and growth projections.&lt;/p&gt;
&lt;p&gt;Sometimes, starting with separate services is genuinely the right choice. Sometimes, a traditional monolith with clear internal structures is simpler and more honest than pretending you’re building something modular.&lt;/p&gt;
&lt;p&gt;So it’s not precise to say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“If you cannot deal with monolith, microservices won’t work for you!”&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The actual phrase should be: &lt;em&gt;&lt;strong&gt;if you can’t deal with modularity, then neither monoliths nor microservices will work for you.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If you try to cheat and select modulith-first to cheat using a simplistic solution, it won’t help you; you will probably end up with a big ball of mud.&lt;/p&gt;
&lt;p&gt;Monolith-first can give you a gradual process of reaching maturity, but you need to plan it and consider that the DevOps process related to it and tooling is not as simple and mature as glossed.&lt;/p&gt;
&lt;p&gt;So, if you’re considering whether to go with microservices-first or monolith-first, you may be thinking the wrong way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You should go Modular-First and then think about your deployment strategy.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Whatever you choose, you’ll need to do exercise and think about how to solve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;data isolation (modules, tenantc, etc.),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;data consistency (transactional, eventual, causal, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;modules boundaries,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;process boundaries and how you model integrations,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DevOps process,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Code structure,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;how to fit that with your team experience,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;how to fit that with your budget and your product,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Saying to go monolith-first or micro-service first won’t give you proper answers and might not even be a strategy but just a blank statement that will hide the real complexity.&lt;/p&gt;
&lt;p&gt;As always, be pragmatic and pick your poison!&lt;/p&gt;
&lt;p&gt;You may also be interested in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how_to_quickly_scale_a_legacy_monolith/&quot;&gt;How to quickly scale a legacy monolith?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how_to_cut_microservices/&quot;&gt;How (not) to cut microservices&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how_to_design_software_architecture_pragmatically/&quot;&gt;How to design software architecture pragmatically&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[What we don't know that we don't know]]></title><description><![CDATA[Welcome to the next week! I’ve got good news and bad news for you today. The good news is that I no longer have the flu; the bad news is…]]></description><link>https://event-driven.io/en/what-we-dont-know-that-we-dont-know/</link><guid isPermaLink="false">https://event-driven.io/en/what-we-dont-know-that-we-dont-know/</guid><content:encoded>&lt;p&gt;Welcome to the next week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’ve got good news and bad news for you today.&lt;/strong&gt; The good news is that I no longer have the flu; the bad news is that it evolved into pneumonia… So, because of that, I have other news that could be good or bad, depending on how you look at it: &lt;strong&gt;it’ll be quick today.&lt;/strong&gt; But I’ll try not to make it shallow!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I had a weird thought on Friday, right after leaving the&lt;/strong&gt; &lt;strong&gt;hospital.&lt;/strong&gt; I went there because I had complications from my flu. Scratch the details; let’s say they were looking pretty severe. My doctor sent me there to double-check her diagnosis.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fbe91165b49ed877eef5d3df1fd89470/38b44/2025-04-07-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 52.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQDBv/EABYBAQEBAAAAAAAAAAAAAAAAAAIAAf/aAAwDAQACEAMQAAAB2gorjzgxf//EABwQAAEDBQAAAAAAAAAAAAAAAAECAxEAECExMv/aAAgBAQABBQJMAOtRZPAzR3//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAwEBPwGn/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGhAAAQUBAAAAAAAAAAAAAAAAEAABAhFxEv/aAAgBAQAGPwK3XV6ZYP/EABkQAQADAQEAAAAAAAAAAAAAAAEAESExEP/aAAgBAQABPyHeMIAqlYezhNghTrw//9oADAMBAAIAAwAAABDv7//EABYRAQEBAAAAAAAAAAAAAAAAAAEQIf/aAAgBAwEBPxAyz//EABYRAQEBAAAAAAAAAAAAAAAAAAEQIf/aAAgBAgEBPxBNn//EABoQAQADAQEBAAAAAAAAAAAAAAEAESExUXH/2gAIAQEAAT8QvBBWnVgNdqVs3S/GGnNC8S2EQgaUbr2dvs//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/fbe91165b49ed877eef5d3df1fd89470/507e6/2025-04-07-cover.webp 200w,
/static/fbe91165b49ed877eef5d3df1fd89470/28a80/2025-04-07-cover.webp 400w,
/static/fbe91165b49ed877eef5d3df1fd89470/8d2ea/2025-04-07-cover.webp 800w,
/static/fbe91165b49ed877eef5d3df1fd89470/68fc1/2025-04-07-cover.webp 1200w,
/static/fbe91165b49ed877eef5d3df1fd89470/4b075/2025-04-07-cover.webp 1280w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/fbe91165b49ed877eef5d3df1fd89470/37402/2025-04-07-cover.jpg 200w,
/static/fbe91165b49ed877eef5d3df1fd89470/4cda9/2025-04-07-cover.jpg 400w,
/static/fbe91165b49ed877eef5d3df1fd89470/c60e9/2025-04-07-cover.jpg 800w,
/static/fbe91165b49ed877eef5d3df1fd89470/6c738/2025-04-07-cover.jpg 1200w,
/static/fbe91165b49ed877eef5d3df1fd89470/38b44/2025-04-07-cover.jpg 1280w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/fbe91165b49ed877eef5d3df1fd89470/c60e9/2025-04-07-cover.jpg&quot;
            alt=&quot;2025 04 07 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I spent over 5 hours in the hospital, going through several medical treatments. In the end, it appeared that the initial diagnosis was right: pneumonia. I was released home with the note that prescribed antibiotics should be good enough.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And here we’re coming to my weird thought.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I was impressed by how I was treated. I was informed of what was happening at each stage, why, and what was next.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That gave me trust, that people knew what they were doing and that I was in good hands, but the weird thought was: but do I?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I know that I know? How do I know that I don’t know?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It reminded me of my other discussion with my Grandpa. We were watching some political debate when the TV signal went down. My Grandpa, in anger, said that it must “have been our prime minister acting not to let us see his opponent in the upcoming elections.”&lt;/p&gt;
&lt;p&gt;I, of course, said, &lt;em&gt;“Grandpa, I don’t like him either, but that’s just technical issue”&lt;/em&gt;, and I got the answer: &lt;em&gt;“But how do you know, can you prove that’s not him?”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And, well, I couldn’t say that I know that for 100% and I could prove that.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Common sense, aye? Actually, not, it can lead to multiple thinking issues like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;if something is not good, then it’s bad,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;if something is not bad, then it’s good,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we can only judge decision by the outcome of it,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we can never judge decisions, because we never know,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Or the worst of the worst: &lt;strong&gt;“Challenge status quo!”.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Just like I didn’t know that our ex-prime minister turned off signal in my TV, I didn’t know that the decision made by doctors was actually right.&lt;/strong&gt; Should I challenge the status quo and not take antibiotics? There is a place for such people: it’s called a cemetery.&lt;/p&gt;
&lt;p&gt;So how do I know that I know, or how do I know that I don’t know? How to find the middle ground?&lt;/p&gt;
&lt;p&gt;The first thing is to realise that we may be wrong and start to think about what to do about it. I wrote on it longer in &lt;a href=&quot;/en/the_risk_of_ignoring_risks/&quot;&gt;The risk of ignoring risks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The second is to differentiate decision from outcome.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Just like in Poker, we should calculate the odds and consider the risk of decision-making. In Poker, we can have the best situation, but the chance is never 100% that we’re right. If we see that we have, e.g., 90% of the chance to win looking at our hand and cards on the table, then making a specific decision can be right, but the outcome may still be a lost game. Those 10% can be enough if we have bad luck.&lt;/p&gt;
&lt;p&gt;And in reality, we can have bad luck. But that doesn’t invalidate our decision, as in 9 of 10 cases, we’d be right. Should the random outcome invalidate our decision?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But okay, how do we know it’s 90%? It’s statistics based on the data we gathered throughout the game.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In our system design, that’s the homework we’re doing through preparation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;modeling,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;design,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;challenging our design with our stakeholders and peers,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;proof of concepts,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;validations,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;simulations,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So that’s what I get in the hospital. Even though I ended up with the same diagnosis as I would have without it, it was much more backed with evidence and trustworthy.&lt;/p&gt;
&lt;p&gt;And now the bonus point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remember that style matters. Someone can make bullshit sounds reasonable, and someone can’t make a great idea sounds tempting. It’s not enough to be right. You need to be persuasive.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Quite often, our goal as engineers or architects is to contribute to the general business plan focused on technical analysis. Too often, the final decision is made by people who have no idea if we’re right or wrong. They don’t know what they don’t know.&lt;/p&gt;
&lt;p&gt;That’s a responsibility on us to prepare and to build trust in our professionalism. That’s why we need to learn to talk to other people involved in the process. Explain to them what’s happening, why and what’s next.&lt;/p&gt;
&lt;p&gt;If we try to follow that, the chance that our ideas will be accepted grows.&lt;/p&gt;
&lt;p&gt;And folks, please, do our homework, and make our idea be closer to hospital analysis than conspiracy theories. Or licked finger and the wind technique.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And folks, most importantly, stay healthy, that’s much more important than our work.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[We are no superhumans]]></title><description><![CDATA[Welcome to the next week! If you want to make God laugh, tell him about your plans. Everyone has a plan until they get punched in the face…]]></description><link>https://event-driven.io/en/we-are-no-superhumans/</link><guid isPermaLink="false">https://event-driven.io/en/we-are-no-superhumans/</guid><content:encoded>&lt;p&gt;Welcome to the next week!&lt;/p&gt;
&lt;p&gt;If you want to make God laugh, tell him about your plans. Everyone has a plan until they get punched in the face. We can and should make plans but also learn to let go.&lt;/p&gt;
&lt;p&gt;We are no superhumans, or at least I am not.&lt;/p&gt;
&lt;p&gt;Sometimes, we have a bad week at work, sometimes our daughter falls and ends up with three stitches in her head, and sometimes we wake up feeling terrible with the flu. Surprisingly, such cases always happen together, aye?&lt;/p&gt;
&lt;p&gt;All of that has happened to me in the last few days, especially the nasty flu, which stopped me from delivering the proper Architecture Weekly this week. And also stopped from going to conference, and maybe made me postpone the workshop…&lt;/p&gt;
&lt;p&gt;Still, not to leave you with nothing, I have prepared a &lt;a href=&quot;https://www.youtube.com/watch?v=jnDchr5eabI&amp;#x26;list=PLw-VZz_H4iiqUeEBDfGNendS0B3qIk-ps&quot;&gt;playlist with the 22 recordings of my conference talks&lt;/a&gt;. You’ll find there a lot of materials around Event-Driven Architecture, CQRS, and Event Sourcing.&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/0pYmuk0-N_4?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[TypeScript Migrates to Go: What's Really Behind That 10x Performance Claim?]]></title><description><![CDATA[Welcome to the new week! Almost two weeks ago, Microsoft announced they’re porting the TypeScript compiler from JavaScript to Go, promising…]]></description><link>https://event-driven.io/en/typescript-migrates-to-go-whats-really/</link><guid isPermaLink="false">https://event-driven.io/en/typescript-migrates-to-go-whats-really/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Almost two weeks ago, &lt;a href=&quot;https://devblogs.microsoft.com/typescript/typescript-native-port/&quot;&gt;Microsoft announced they’re porting the TypeScript compiler from JavaScript to Go, promising a staggering 10x performance improvement&lt;/a&gt;.&lt;/strong&gt; This news spread like wildfire across tech communities, with everyone from TypeScript fans to language war enthusiasts chiming in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The announcement leads with impressive performance numbers and ambitious goals, though some intriguing details remain unexplored.&lt;/strong&gt; We’ll tackle those today. Why today and not a week ago? Well, in Architecture Weekly, we don’t want rushy clickbaits, aye? I intentionally wanted to wait for the noise to calm down.&lt;/p&gt;
&lt;p&gt;Beneath the headline figures lies a story worth unpacking about design choices, performance trade-offs, and the evolution of developer tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Even if you’re not interested in compilers, there are suitable lessons learned for your system’s design, like:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Looking beyond headline performance claims&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Matching your technology to your problem domain&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understand your runtime model.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reconsidering foundations as projects evolve.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s tackle that step by step, starting from the Microsoft article headline.&lt;/p&gt;
&lt;h2 id=&quot;a-10x-faster-typescript--well-not-exactly&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#a-10x-faster-typescript--well-not-exactly&quot; aria-label=&quot;a 10x faster typescript  well not exactly permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;“A 10x Faster TypeScript” – Well, Not Exactly&lt;/h2&gt;
&lt;p&gt;First, let’s clarify something that might be confusing in Microsoft’s announcement title:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“A 10x Faster TypeScript.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Is it really faster? Would your application written in TypeScript be 10x faster once MS releases the new code rewritten in Go?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/92fb8ebf69805c47fbcbc88fdae4fa97/image-2.gif&quot; alt=&quot;Not Really GIFs | Tenor&quot; title=&quot;Not Really GIFs | Tenor&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s actually getting faster is the TypeScript compiler, not the TypeScript language itself or the JavaScript’s runtime performance.&lt;/strong&gt; Your TypeScript code will compile faster, but it won’t suddenly execute 10x faster in the browser or Node.js.&lt;/p&gt;
&lt;p&gt;It’s a bit like saying,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We made your car 10x faster!”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and then clarifying that they only made the manufacturing process faster—the car itself still drives at the same speed. It’s still valuable, especially if you’ve been waiting months for your car, but not quite what the headline suggests.&lt;/p&gt;
&lt;h2 id=&quot;beyond-the-10x-faster-headline&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#beyond-the-10x-faster-headline&quot; aria-label=&quot;beyond the 10x faster headline permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Beyond the “10x Faster” Headline&lt;/h2&gt;
&lt;p&gt;Anders Hejlsberg’s announcement showcased impressive numbers:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 478px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/234877a93d9743bf5279de5c03e212b5/69adc/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 57.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAACDUlEQVR42lVTWZaiQBDkDC64t2BRUMWmAorKE7X1Z+5/oJiKtO1585GvtsglIrM83/eh4wRpliFztlwuMRwOsV6vYa1FalMxnhOHM8a4fYCyLBHHMYIgdLjU+eYIwxDedOLDmgTVvsLhcITabDCZTLDZKHHKHZAOSikURSFJNw5TV7XsoyhC0xywd/6xS+htLy8U5xeMzVwFMdI0FSDtdDrj+/uJ+/2OPM8lwePxeCdy567rBE8mfd+jrmt4YVIgNCVCxfLXQof0giBAkiTiyOCkw8oYgCvNGis4vvGe1XpL22KZ1JirEiOn3Wg0+rXVaoXtduvAGSbTKabOlIrcOgO1Z4DxeCx7rWMJ7tldi7TqYIq9UGZVHyvyAu2xddoe3g1yVdR180uzqippEq1ymlJjzzY90uMDDBzrSDr3MWp1Op3Qtq04MdAnOM9N0wjOJEbuWYA31ztMA/MfVRqpLBYL0ZABSJfUGIB7vmmtBTcYDGQKZGxUVkNZ1xQ3Jp9m8IErhf+MCu/e4mc/TVGSiLivry9pEGXyyu4PussFDzcapNBfezyfL6HLYLfb/d+oFDmu16vsOYe3/ibJKMXlfHGzuIe3VsZpF0tW/bPO53P5MVxJhZnZ8fc5kj2rImVSp7Hj9PUCs8Nuy+yFdI2g4XAkv4VaEsQqZrOZ6BVFWgIzCBtDXX1/Ir9ERxp/AfBrXHrWGMxuAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/234877a93d9743bf5279de5c03e212b5/507e6/image-3.webp 200w,
/static/234877a93d9743bf5279de5c03e212b5/28a80/image-3.webp 400w,
/static/234877a93d9743bf5279de5c03e212b5/ca276/image-3.webp 478w&quot;
              sizes=&quot;(max-width: 478px) 100vw, 478px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/234877a93d9743bf5279de5c03e212b5/36ca5/image-3.png 200w,
/static/234877a93d9743bf5279de5c03e212b5/a3397/image-3.png 400w,
/static/234877a93d9743bf5279de5c03e212b5/69adc/image-3.png 478w&quot;
            sizes=&quot;(max-width: 478px) 100vw, 478px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/234877a93d9743bf5279de5c03e212b5/69adc/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Such remarkable stats deserve a deeper look, as this 10x speedup has multiple contributing factors. It’s not simply that &lt;em&gt;“Go is faster than JavaScript”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let’s be honest: whenever you see a “10x faster” claim, you should view it with healthy scepticism.&lt;/strong&gt; My first reaction was, “OK, what did they do suboptimal before?” Because 10x improvements don’t materialize from thin air—they typically indicate that something wasn’t made well enough in the first place. Be it implementation or some design choices.&lt;/p&gt;
&lt;p&gt;There’s a widespread belief that “Node.js is slow,” which is more of a repeated stereotype than a truth. In some instances, it can be true, but it is not a general statement.&lt;/p&gt;
&lt;p&gt;If someone says, “Node.js is slow” is slow, it’s almost like they’d say that C and C++ are slow. Why?&lt;/p&gt;
&lt;h3 id=&quot;the-architecture-of-nodejs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-architecture-of-nodejs&quot; aria-label=&quot;the architecture of nodejs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Architecture of Node.js&lt;/h3&gt;
&lt;p&gt;Node.js is built on Google’s &lt;a href=&quot;https://v8.dev/&quot;&gt;V8 JavaScript engine&lt;/a&gt;, which is the same high-performance engine that powers Chrome. The V8 engine itself is written in C++, and Node.js essentially provides a runtime environment around it. This architecture is key to understanding Node.js performance:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;V8 Engine&lt;/strong&gt;: Compiles JavaScript to machine code using Just-In-Time (JIT) compilation techniques&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://libuv.org/&quot;&gt;libuv&lt;/a&gt;&lt;/strong&gt;: A C library that handles asynchronous I/O operations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Core Libraries&lt;/strong&gt;: Many written in C/C++ for performance. That’s also why there are few significant performance differences between performance of database connectors used in Node.js, Go and Rust.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JavaScript APIs&lt;/strong&gt;: Thin wrappers around these native implementations&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When people talk about Node.js, they often don’t realize they’re talking about a system where critical operations are executed by highly optimized C/C++ code. Your JavaScript often just orchestrates calls to these native implementations.&lt;/p&gt;
&lt;p&gt;Fun fact: Node.js has been among the fastest web server technologies available for many years. When it first appeared, it outperformed many traditional threaded web servers in benchmarks, especially for high-concurrency, low-computation workloads. This wasn’t an accident—it was by design.&lt;/p&gt;
&lt;h3 id=&quot;memory-bound-vs-cpu-bound&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#memory-bound-vs-cpu-bound&quot; aria-label=&quot;memory bound vs cpu bound permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Memory-Bound vs. CPU-Bound&lt;/h3&gt;
&lt;p&gt;Node.js was specifically architected for web servers and networked applications, which are predominantly &lt;strong&gt;memory-bound and I/O-bound&lt;/strong&gt; rather than CPU-bound:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Memory-bound operations&lt;/strong&gt; involve moving data around, transforming it, or storing/retrieving it. Think of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Parsing JSON payloads&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Transforming data structures&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Routing HTTP requests&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Formatting response data&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;I/O-bound operations&lt;/strong&gt; involve waiting for external systems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Database queries&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Network requests&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;File system operations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;External API calls&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For typical web applications, most of the time is spent waiting for these I/O operations to complete. A typical request flow might be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Receive HTTP request (memory-bound)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Parse request data (memory-bound)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Query database (I/O-bound, mostly waiting)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Process results (memory-bound)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Format response (memory-bound)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Send HTTP response (I/O-bound)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this workflow, actual CPU-intensive computation is minimal. Most web applications spend 80-90% of their time waiting for I/O operations to complete.&lt;/p&gt;
&lt;p&gt;Node.js was optimised for precisely for this scenario because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Non-blocking I/O&lt;/strong&gt;: While waiting for I/O operations, Node.js can handle other requests&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;C++ Foundation&lt;/strong&gt;: Memory operations delegate to efficient C++ implementations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Event Loop Efficiency&lt;/strong&gt;: Great at coordinating many concurrent operations with minimal overhead&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here’s what many people miss: &lt;strong&gt;I/O-intensive operations in Node.js work at nearly C-level speeds&lt;/strong&gt;. When you make a network request or read a file in Node.js, you’re essentially calling C functions with a thin JavaScript wrapper.&lt;/p&gt;
&lt;p&gt;This architecture made Node.js revolutionary for web servers. When properly used, a single Node.js process can efficiently handle thousands of concurrent connections, often outperforming thread-per-request models for typical web workloads.&lt;/p&gt;
&lt;p&gt;It came essentially from the idea that multitasking, like in human tasks, is not always the optimal way of handling tasks. Synchronising multiple tasks and context switching adds overhead and does not always give better results. It all depends on whether a task can be split into smaller pieces that can be done concurrently.&lt;/p&gt;
&lt;h2 id=&quot;where-nodejs-faces-challenges-cpu-bound-operations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#where-nodejs-faces-challenges-cpu-bound-operations&quot; aria-label=&quot;where nodejs faces challenges cpu bound operations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Where Node.js Faces Challenges: CPU-Bound Operations&lt;/h2&gt;
&lt;p&gt;The real performance challenges for Node.js are CPU-intensive tasks—like (welcome again!) compiling TypeScript. These workloads have fundamentally different characteristics from the web server scenarios for which Node.js was optimized.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CPU-bound operations&lt;/strong&gt; involve heavy computation with minimal waiting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Complex algorithms and calculations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Parsing and analyzing large files&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Image/video processing&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compiling code&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In these scenarios, the bottleneck isn’t waiting for external systems—it’s raw computational power and how efficiently the runtime can execute algorithms.&lt;/p&gt;
&lt;h3 id=&quot;the-single-threaded-limitation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-single-threaded-limitation&quot; aria-label=&quot;the single threaded limitation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Single-Threaded Limitation&lt;/h3&gt;
&lt;p&gt;JavaScript was designed with a single-threaded event loop model. This model works best for handling concurrent I/O (where most time is spent waiting) but becomes problematic for CPU-intensive operations:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Pseudocode of how the Node.js event loop works&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;thereAreEvents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; event &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getNextEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;processEvent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// If this takes a long time,&lt;/span&gt;
                        &lt;span class=&quot;token comment&quot;&gt;// everything else waits&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When a CPU-intensive task runs, it monopolizes this single thread. During that time, Node.js can’t process other events, handle new requests, or even respond to existing ones. It’s effectively blocked until the computation completes.&lt;/p&gt;
&lt;p&gt;This is why running a complex algorithm in Node.js can make your entire web server unresponsive—the event loop is busy with computation and can’t handle incoming requests.&lt;/p&gt;
&lt;h3 id=&quot;the-event-loop&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-event-loop&quot; aria-label=&quot;the event loop permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Event Loop&lt;/h3&gt;
&lt;p&gt;Writing efficient CPU-intensive code in JavaScript requires understanding and respecting the &lt;a href=&quot;https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#the-nodejs-event-loop&quot;&gt;event loop&lt;/a&gt;. The code must be structured to yield control, allowing other operations to proceed periodically:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//////////////////////////////////////////////&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Naive approach - blocks the event loop&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/////////////////////////////////////////////&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;processLargeData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Heavy computation that might take seconds&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;processItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; results
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;
&lt;span class=&quot;token comment&quot;&gt;//////////////////////////////////&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// Event-loop friendly approach&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;//////////////////////////////////&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;processLargeDataChunked&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; results &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CHUNK_SIZE&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CHUNK_SIZE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; chunk &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;CHUNK_SIZE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Process one chunk&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; item &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; chunk&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      results&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;processItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Yield to the event loop before processing the next chunk&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; results
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This “chunking” approach works but introduces complexity and fundamentally changes how you structure your code. It’s a very different programming model from languages with native threading, where you can write straightforward CPU-intensive code without worrying about blocking other operations.&lt;/p&gt;
&lt;p&gt;For a complex application like the TypeScript compiler, this dance with the event loop becomes increasingly difficult to manage as the codebase grows.&lt;/p&gt;
&lt;p&gt;Read also more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://nodejs.org/en/learn/asynchronous-work/event-loop-timers-and-nexttick#the-nodejs-event-loop&quot;&gt;Node.js docs: the Node.js Event Loop&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model&quot;&gt;MDN - JavaScript execution model&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;compilers-the-cpu-intensive-beast&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#compilers-the-cpu-intensive-beast&quot; aria-label=&quot;compilers the cpu intensive beast permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Compilers: The CPU-Intensive Beast&lt;/h2&gt;
&lt;p&gt;A compiler is practically the poster child for CPU-intensive workloads. It needs to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Parse source code into tokens and abstract syntax trees&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Perform complex type-checking and inference&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apply transformations and optimizations&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Generate output code&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These operations involve complex algorithms, large memory structures, and lots of computation—exactly the kind of work that challenges JavaScript’s execution model.&lt;/p&gt;
&lt;p&gt;For TypeScript specifically, as the language grew more complex and powerful over the years, the compiler had to handle increasingly sophisticated type checking, inference, and code generation. This progression naturally pushed against the limits of what’s efficient in a JavaScript runtime.&lt;/p&gt;
&lt;h2 id=&quot;threading-models-matter-event-loop-vs-native-concurrency&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#threading-models-matter-event-loop-vs-native-concurrency&quot; aria-label=&quot;threading models matter event loop vs native concurrency permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Threading Models Matter: Event Loop vs. Native Concurrency&lt;/h2&gt;
&lt;p&gt;The performance gap between the JavaScript and Go implementations isn’t just about raw language speed—it’s fundamentally about threading models and how they match the problem domain.&lt;/p&gt;
&lt;p&gt;As mentioned, Node.js operates on an event loop model:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;   ┌───────────────────────────┐
┌─&gt;│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │&amp;lt;─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This single-threaded approach means that for CPU-intensive work like compiling TypeScript, you need to write code that doesn’t monopolize the thread. In practice, this involves breaking work into smaller chunks that can yield control back to the event loop.&lt;/p&gt;
&lt;p&gt;For a compiler, this creates significant design challenges:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Artificial Fragmentation&lt;/strong&gt;: The natural flow of compiler phases (parse → analyze → transform → generate) needs to be broken into small steps that can yield.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Complex State Management&lt;/strong&gt;: Since processing is fragmented across event loop iterations, the compiler state must be carefully managed and preserved between yields.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Locality Disruption&lt;/strong&gt;: When the event loop processes unrelated tasks between compiler operations, CPU cache locality benefits are lost, hurting performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dependency Challenges&lt;/strong&gt;: Compilers have complex interdependencies between components. Breaking a naturally sequential process to accommodate an event loop often requires complex coordination logic.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;go-native-concurrency-with-goroutines&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#go-native-concurrency-with-goroutines&quot; aria-label=&quot;go native concurrency with goroutines permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Go: Native Concurrency with Goroutines&lt;/h3&gt;
&lt;p&gt;Go, by contrast, offers goroutines—lightweight threads managed by the Go runtime:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│Goroutine│ │Goroutine│ │Goroutine│ │Goroutine│ ...  more
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
     │           │           │           │
     └───────────┴───────────┴───────────┘
                       │
              ┌────────┴────────┐
              │   Go Scheduler  │
              └────────┬────────┘
                       │
                ┌──────┴──────┐
                │  OS Threads │
                └─────────────┘&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This model allows compiler phases to be naturally parallelized with minimal coordination overhead:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Natural Parallelism&lt;/strong&gt;: Different files can be parsed and type-checked concurrently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Direct Thread Access&lt;/strong&gt;: CPU-intensive operations can run directly on threads without yielding.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Efficient Coordination&lt;/strong&gt;: Go’s channels and synchronization primitives are designed to coordinate concurrent work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Memory Efficiency&lt;/strong&gt;: Goroutines use minimal memory (a few KB each) compared to OS threads (MBs each).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In this model, file parsing, type checking, and code generation can all happen concurrently without explicit yield points. The code structure can more naturally follow the logical flow of compiler phases.&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://jayconrod.com/posts/128/goroutines-the-concurrency-model-we-wanted-all-along&quot;&gt;Goroutines: the concurrency model we wanted all along by Jay Conrod&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://shane.ai/posts/threads-and-goroutines/&quot;&gt;Threads and Goroutines by Shane Hansen&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;same-code-different-execution-model&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#same-code-different-execution-model&quot; aria-label=&quot;same code different execution model permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Same Code, Different Execution Model&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Anders Hejlsberg said they evaluated multiple languages, and Go was the easiest to port the code base into.&lt;/strong&gt; The TypeScript team has apparently created a tool that generates Go code, resulting in a port that’s nearly line-for-line equivalent in many places. That might lead you to think the code is “doing the same thing,” but that’s a misconception.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/06919797c203d9bd4ef2d0cfba558392/image-4.gif&quot; alt=&quot;Theyre The Same Picture The Office GIF - Theyre The Same Picture The Office  Pam - Discover &amp;#x26; Share GIFs&quot; title=&quot;Theyre The Same Picture The Office GIF - Theyre The Same Picture The Office  Pam - Discover &amp;#x26; Share GIFs&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The code may look the same but can behave very differently across languages due to their execution models.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In JavaScript:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;All code runs on a single thread with an event loop,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Long-running operations need to be broken up or delegated to worker threads,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Concurrent execution requires careful handling to avoid blocking the event loop.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Go:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Code naturally runs across multiple goroutines (lightweight threads),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Long-running operations can execute without blocking other work,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The language and runtime are designed for concurrent execution.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you port code from JavaScript to Go without changing its structure, you implicitly change how it executes. Operations that would block the event loop in JavaScript can run concurrently in Go with minimal effort.&lt;/p&gt;
&lt;p&gt;This can lead to a conclusion: &lt;strong&gt;when a direct port yields dramatic performance improvements, it might indicate that the original implementation wasn’t fully optimized for JavaScript’s execution model&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Writing truly performant JavaScript means embracing its asynchronous nature and event loop constraints—something that becomes increasingly challenging in complex codebases like a compiler.&lt;/p&gt;
&lt;h2 id=&quot;but-what-about-worker-threads&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#but-what-about-worker-threads&quot; aria-label=&quot;but what about worker threads permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;But What About Worker Threads?&lt;/h2&gt;
&lt;p&gt;Some of you might be thinking:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Doesn’t Node.js have &lt;a href=&quot;https://nodejs.org/api/worker_threads.html&quot;&gt;Worker Threads&lt;/a&gt; now? Couldn’t they have used those instead of migrating to Go?”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It’s a fair question. Node.js introduced the &lt;code class=&quot;language-text&quot;&gt;worker_threads&lt;/code&gt; module in v10 as an experimental feature became stable in v12 (mid-2019). Worker threads provide true parallelism in Node.js, allowing CPU-intensive tasks to run on separate threads without blocking the main event loop.&lt;/p&gt;
&lt;h3 id=&quot;how-worker-threads-work-in-nodejs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-worker-threads-work-in-nodejs&quot; aria-label=&quot;how worker threads work in nodejs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How Worker Threads Work in Node.js&lt;/h3&gt;
&lt;p&gt;Unlike the single-threaded event loop that characterizes most Node.js applications, worker threads allow JavaScript to be executed in parallel:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// main.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Worker &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;worker_threads&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;runWorker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;workerData&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; worker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;./worker.js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; workerData &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    worker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;message&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; resolve&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    worker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;error&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    worker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;exit&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; code &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Worker stopped with exit code &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;code&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Run multiple CPU-intensive tasks in parallel&lt;/span&gt;
&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;runWorker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; chunk&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; middleIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;runWorker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; chunk&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;middleIndex&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;results &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Combine results&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// worker.js&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; parentPort&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; workerData &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;worker_threads&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Perform CPU-intensive work&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;processData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;workerData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Send the result back to the main thread&lt;/span&gt;
parentPort&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each worker runs in its own V8 instance with its own JavaScript heap, allowing true parallelism. Workers communicate with the main thread (and each other) by sending and receiving messages, which can include transferring ownership of certain data types to avoid copying.&lt;/p&gt;
&lt;h3 id=&quot;why-werent-worker-threads-the-solution-for-typescript&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-werent-worker-threads-the-solution-for-typescript&quot; aria-label=&quot;why werent worker threads the solution for typescript permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Weren’t Worker Threads the Solution for TypeScript?&lt;/h3&gt;
&lt;p&gt;So why might the TypeScript team have chosen to Go over retrofitting the existing codebase with worker threads? That we don’t know, but there are several plausible reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Legacy Codebase Challenges&lt;/strong&gt;: The TypeScript compiler has been developing for over a decade. Retrofitting a large, mature codebase designed for single-threaded execution with a multi-threaded architecture is often more complex than starting fresh. Workers communicate primarily through message passing. Restructuring a compiler to operate this way requires fundamentally reimagining how components interact. The video showed that even single-threaded Go was faster, so the code would probably still require modification to better take care of event loop characteristics.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Sharing Complexity&lt;/strong&gt;: Workers have limited ability to share memory. A compiler manipulates complex, interconnected data structures (abstract syntax trees, type systems, etc.) that don’t neatly divide into isolated chunks for parallel processing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance Overhead&lt;/strong&gt;: While worker threads provide parallelism, they come with overhead. Each worker has its own V8 instance with separate memory, and data passed between threads typically needs to be serialized and deserialized. They are not as lightweight as threads or goroutines.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Timeline Mismatch&lt;/strong&gt;: When the TypeScript compiler was designed and implemented (around 2012), worker threads didn’t exist in Node.js. The architecture decisions made early on would have assumed a single-threaded model, making later parallelization more difficult.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dead-End Assessment&lt;/strong&gt;: The team may have concluded that even with worker threads, JavaScript would still impose fundamental limitations for their specific workload that would eventually become bottlenecks again.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Skill Set Alignment&lt;/strong&gt;: The decision may have partly reflected organizational expertise and strategic alignment with other developer tools.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;gos-approach-vs-worker-threads&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#gos-approach-vs-worker-threads&quot; aria-label=&quot;gos approach vs worker threads permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Go’s Approach vs. Worker Threads&lt;/h3&gt;
&lt;p&gt;Go’s approach to concurrency offers several advantages over Node.js worker threads for compiler workloads:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lightweight Goroutines&lt;/strong&gt;: Goroutines are extremely lightweight (starting at ~2KB of memory) compared to worker threads (which require a separate V8 instance), making fine-grained parallelism more practical.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shared Memory Model&lt;/strong&gt;: Go allows direct shared memory between goroutines with synchronization primitives, making working with complex, interconnected data structures easier.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Language-Level Concurrency&lt;/strong&gt;: Concurrency is built into Go at the language level with goroutines and channels, making parallel code more natural to write and reason about.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lower Overhead Communication&lt;/strong&gt;: Communication between goroutines is significantly more efficient than the serialization/deserialization required for worker thread communication.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mature Scheduler&lt;/strong&gt;: Go’s runtime includes a mature, efficient scheduler for managing thousands of goroutines across available CPU cores.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also, I think the migration to Go wasn’t just about &lt;em&gt;“multithreading vs. single-threading”&lt;/em&gt; but about adopting a programming model where concurrency is a first-class concept, deeply integrated into the language and runtime.&lt;/p&gt;
&lt;h2 id=&quot;the-evolution-problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-evolution-problem&quot; aria-label=&quot;the evolution problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Evolution Problem&lt;/h2&gt;
&lt;p&gt;When TypeScript started in 2012, the team made reasonable technology choices based on the context at that time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;TypeScript was a Microsoft project extending JavaScript, so using JavaScript made sense&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The initial scope and complexity were much smaller&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alternatives like Go and Rust were still in their early days&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The performance demands were more modest&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Over time, TypeScript evolved from a relatively simple superset of JavaScript into a sophisticated language with advanced type features, generics, conditional types, and more. The compiler grew accordingly but remained built on foundations designed for a simpler problem.&lt;/p&gt;
&lt;p&gt;This is a classic example of how successful software often faces scaling challenges that weren’t anticipated in its early design. As the TypeScript compiler became more complex and was applied to larger codebases, its JavaScript foundations became increasingly restrictive.&lt;/p&gt;
&lt;p&gt;Don’t you know that on your own?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How is legacy code created? Day by day.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;outstanding-questions-and-future-considerations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#outstanding-questions-and-future-considerations&quot; aria-label=&quot;outstanding questions and future considerations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Outstanding Questions and Future Considerations&lt;/h2&gt;
&lt;p&gt;While the performance improvements from the Go migration are impressive, several important questions haven’t been fully addressed in Microsoft’s announcement:&lt;/p&gt;
&lt;h3 id=&quot;what-about-browser-support&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-about-browser-support&quot; aria-label=&quot;what about browser support permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What About Browser Support?&lt;/h3&gt;
&lt;p&gt;TypeScript doesn’t just run on servers and development machines and’s used directly in browsers via various playground implementations and in-browser IDEs. How will Microsoft address this use case since Go doesn’t run natively in browsers?&lt;/p&gt;
&lt;p&gt;There are a few potential approaches:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;WebAssembly (WASM)&lt;/strong&gt;: Compiling the Go implementation to WebAssembly could allow it to run in browsers. While WASM performance has improved dramatically, there would still be overhead compared to native Go.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dual Implementation&lt;/strong&gt;: Microsoft might maintain a JavaScript version for browsers alongside the Go version for everything else. This would create challenges for feature parity and maintenance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Browser-Specific Alternative&lt;/strong&gt;: They might create a streamlined browser-specific implementation with reduced functionality optimized for common playground scenarios.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cloud Compilation&lt;/strong&gt;: Browser-based tools might send code to cloud endpoints running the Go compiler instead of performing compilation locally.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The announcement doesn’t clarify their approach, and it’s an important detail that will affect the TypeScript ecosystem.&lt;/p&gt;
&lt;h3 id=&quot;feature-parity-vs-performance-trade-offs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#feature-parity-vs-performance-trade-offs&quot; aria-label=&quot;feature parity vs performance trade offs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Feature Parity vs. Performance Trade-offs&lt;/h3&gt;
&lt;p&gt;It’s worth noting that achieving performance improvements often involves trade-offs. One common approach is to reduce feature scope or complexity. While Microsoft claims they’re maintaining full feature parity, we should watch carefully to see if any subtle behaviours change or if certain edge cases are handled differently.&lt;/p&gt;
&lt;p&gt;Historical examples from other language migrations show that 100% identical behaviour is challenging to achieve. Some questions to consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Will all existing TypeScript error messages remain exactly the same?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Will every edge case in type inference behave identically?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Will compilation options and flags have the same effects?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How will performance optimizations affect type system corner cases?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The TypeScript team has a strong track record of backward compatibility, but a ground-up rewrite inherently carries risks of subtle behavioural changes.&lt;/p&gt;
&lt;h3 id=&quot;extensibility-and-plugin-ecosystem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#extensibility-and-plugin-ecosystem&quot; aria-label=&quot;extensibility and plugin ecosystem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Extensibility and Plugin Ecosystem&lt;/h3&gt;
&lt;p&gt;TypeScript has a rich ecosystem of plugins and tools that extend the compiler. The migration to Go raises questions about the future of this ecosystem:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Will the plugin API remain compatible?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Will JavaScript/TypeScript-based plugins need to be rewritten in Go?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How will this affect the barrier to entry for creating TypeScript tooling?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These considerations will affect the broader TypeScript ecosystem beyond just compilation performance.&lt;/p&gt;
&lt;h2 id=&quot;why-this-matters-beyond-typescript&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-this-matters-beyond-typescript&quot; aria-label=&quot;why this matters beyond typescript permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why This Matters Beyond TypeScript&lt;/h2&gt;
&lt;p&gt;This case study has broader implications for technology choices:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Match your technology to your problem domain&lt;/strong&gt;. CPU-bound tasks like compilers benefit from languages designed for computation and native threading. IO-bound tasks like web servers often work well with event-loop models.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reconsider foundations as projects evolve&lt;/strong&gt;. What worked for a small project might become a constraint at scale. Be willing to revisit fundamental architecture decisions. Nothing’s wrong with a bold move, like rewriting if it’s needed. &lt;a href=&quot;/en/so-you-want-to-break-down-monolith/&quot;&gt;But before you do it, read that first&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Look beyond headline performance claims&lt;/strong&gt;. A “10x improvement” often has multiple contributing factors beyond just the change in the technology stack.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Understand your runtime model&lt;/strong&gt;. Whether you use Node.js, Go, Rust, or any other environment, understanding how code executes is crucial for performance optimization.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;looking-forward&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#looking-forward&quot; aria-label=&quot;looking forward permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Looking Forward&lt;/h2&gt;
&lt;p&gt;As a TypeScript user, building Emmett and Pongo is good news. If I can get the compiler working faster “for free”, that’s sweet. But on the other hand, I don’t see why we make such noise and use clickbait as a way of bringing that to the development community.&lt;/p&gt;
&lt;p&gt;Focusing on 10x without giving enough context just created friction (I mercifully skipped C# developers’ cries of “why not C#?!“…).&lt;/p&gt;
&lt;p&gt;That’s why I wanted to expand on this use case, as it offers valuable lessons about technology, language choices, performance optimization, and the evolution of successful projects.&lt;/p&gt;
&lt;p&gt;The move from JavaScript to Go shouldn’t be taken as a confirmation that “Node.js is slow”. It’s better to look on that as a recognition that different problems call for different tools. JavaScript and Node.js continue to be good at what they were designed for: IO-intensive web applications with high concurrency needs.&lt;/p&gt;
&lt;p&gt;Of course, it’d be better if Microsoft explained that in more detail, rather than making a clickbait claim, but this is the world we live in.&lt;/p&gt;
&lt;p&gt;Check also previous releases on performance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/talk-is-cheap-show-me-the-numbers/&quot;&gt;Talk is cheap, show me the numbers! Benchmarking and beyond!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-22-on-performance-testing/&quot;&gt;On Performance Testing with Jarosław Pałka&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What do you think? Have you faced challenges similar to technology evolution in your projects? How did you tackle them? Did you ever get a 10x surprising improvement? How did you deal with that?&lt;/p&gt;
&lt;p&gt;I’d love to hear your thoughts in the comments!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Whole Architecture Weekly content is free now!]]></title><description><![CDATA[Hi, the only constant in the world is change. Seven months ago, I made a big change and made the Architecture Weekly a paid newsletter. Now…]]></description><link>https://event-driven.io/en/whole-architecture-weekly-content/</link><guid isPermaLink="false">https://event-driven.io/en/whole-architecture-weekly-content/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Hi, the only constant in the world is change. Seven months ago, I made a big change and &lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;made the Architecture Weekly a paid newsletter&lt;/a&gt;. Now, I have decided to make it fully free.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d7282f3ea662e2296c231f3599000806/d6994/2025-03-17-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBP/EABUBAQEAAAAAAAAAAAAAAAAAAAED/9oADAMBAAIQAxAAAAF6Zrg5Im//xAAZEAEBAQEBAQAAAAAAAAAAAAABAgMSADH/2gAIAQEAAQUC10EnWU78HUmczT9//8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQMBAT8BR//EABYRAQEBAAAAAAAAAAAAAAAAAAABEv/aAAgBAgEBPwFuv//EABkQAAMBAQEAAAAAAAAAAAAAAAABETEQQf/aAAgBAQAGPwL1GOmMr2ii5//EABsQAAIDAAMAAAAAAAAAAAAAAAABESExQWGh/9oACAEBAAE/IUhW7oq8SxrII05F4PmKVdlXXZ//2gAMAwEAAgADAAAAEHTv/8QAFhEBAQEAAAAAAAAAAAAAAAAAEQAh/9oACAEDAQE/EDCN/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARQf/aAAgBAgEBPxAVN7IOX//EAB0QAAICAgMBAAAAAAAAAAAAAAERACExYVGB0aH/2gAIAQEAAT8QrtGgyrUQ6IGgzzmF+D2JsxhqgCruM4l2X91AAIBAgJ//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d7282f3ea662e2296c231f3599000806/507e6/2025-03-17-cover.webp 200w,
/static/d7282f3ea662e2296c231f3599000806/28a80/2025-03-17-cover.webp 400w,
/static/d7282f3ea662e2296c231f3599000806/8d2ea/2025-03-17-cover.webp 800w,
/static/d7282f3ea662e2296c231f3599000806/18c85/2025-03-17-cover.webp 1199w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d7282f3ea662e2296c231f3599000806/37402/2025-03-17-cover.jpg 200w,
/static/d7282f3ea662e2296c231f3599000806/4cda9/2025-03-17-cover.jpg 400w,
/static/d7282f3ea662e2296c231f3599000806/c60e9/2025-03-17-cover.jpg 800w,
/static/d7282f3ea662e2296c231f3599000806/d6994/2025-03-17-cover.jpg 1199w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d7282f3ea662e2296c231f3599000806/c60e9/2025-03-17-cover.jpg&quot;
            alt=&quot;2025 03 17 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;What’s more, I made all past content available for free! &lt;strong&gt;You can read all past articles now:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;Mastering Database Connection Pooling&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;Queuing, Backpressure, Single Writer and other useful patterns for managing concurrency&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-191-is-it-production/&quot;&gt;Is It Production Ready?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/talk-is-cheap-show-me-the-numbers/&quot;&gt;Talk is cheap, show me the numbers! Benchmarking and beyond!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/using-s3-but-not-the-way-you-expected/&quot;&gt;Using S3 but not the way you expected. S3 as strongly consistent event store&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/show-me-the-money-practically-navigating/&quot;&gt;Show me the money! Practically navigating the Cloud Costs Complexity&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/when-logs-and-metrics-arent-enough/&quot;&gt;When Logs and metrics are not enough: Discovering Modern Observability&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/making-your-system-observability/&quot;&gt;Making your system observability predictable&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/why-to-measure-and-make-our-system/&quot;&gt;Why to measure and make our system observable? How to reason on chaotic world&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/dont-oversell-ideas-trunk-based-development/&quot;&gt;Don’t Oversell Ideas: Trunk-Based Development Edition&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/tech-debt-doesnt-exist-but-trade/&quot;&gt;Tech Debt doesn’t exist, but trade-offs do&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/building-your-own-ledger-database/&quot;&gt;Building your own Ledger Database&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/ordering-grouping-and-consistency/&quot;&gt;Ordering, Grouping and Consistency in Messaging systems&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/deduplication-in-distributed-systems/&quot;&gt;Deduplication in Distributed Systems: Myths, Realities, and Practical Solutions&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/the-write-ahead-log-a-foundation/&quot;&gt;The Write-Ahead Log: The underrated Reliability Foundation for Databases and Distributed systems&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/on-getting-the-meaningful-discussions/&quot;&gt;On getting the meaningful discussions, and why that’s important&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/distributed-locking-a-practical-guide/&quot;&gt;Distributed Locking: A Practical Guide&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/locks-queues-and-business-workflows/&quot;&gt;Locks, Queues and business workflows processing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/thoughts-on-platforms-core-teams/&quot;&gt;Thoughts on Platforms, Core Teams, DORA Report and all that jazz&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how-a-kafka-like-producer-writes/&quot;&gt;How Kafka Producer Works internally&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/kafka-consumers-under-the-hood-of/&quot;&gt;Kafka Consumers: Under the Hood of Message Processing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/understanding-kafkas-consumer-protocol/&quot;&gt;Understanding Kafka’s Consumer Protocol: A Deep Dive into How Consumers Talk to Brokers&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/defining-your-paranoia-level-navigating/&quot;&gt;Defining Your Paranoia Level: Navigating Change Without the Overkill&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how-does-kafka-know-what-was-the/&quot;&gt;How does Kafka know what was the last message it processed? Deep dive into Offset Tracking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/so-you-want-to-break-down-monolith/&quot;&gt;So you want to break down monolith? Read that first.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;And watch all past webinars:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing&quot;&gt;#1 - From CRUD to Event Sourcing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short&quot;&gt;#2 - Keep your streams short! Or how to model Event-Sourced systems efficiently&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;#3 - Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice&quot;&gt;#4 - From CRUD to CQRS in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100&quot;&gt;#5 - Architecture Weekly 100 Edition - Live Q&amp;#x26;A&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;#6 - Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven&quot;&gt;#7 - Design and test Event-Driven projections and read models&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates&quot;&gt;#8 - Slim down your aggregates!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;#9 - Radek Maziarka - Modularization with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;#10 - PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski&quot;&gt;#11 - Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify&quot;&gt;#12 - Jeremy D. Miller: Simplify your architecture with Wolverine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic&quot;&gt;#13 - Yves Goeleven - The Fantastic 9&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-14-mateusz-jendza-why-verified&quot;&gt;#14 - Mateusz Jendza - Why Verified Credentials is the Future of Digital Identity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-15-mario-bittencourt-leveraging&quot;&gt;#15 - Mário Bittencourt: Leveraging BPMN for Seamless Team Collaboration in Software Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-1-sagas-hector-garcia&quot;&gt;#16 - Papers We Love #1 - Sagas (Hector Garcia-Molina, Kenneth Salem)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-16-simple-patterns-for-events&quot;&gt;#17 - Simple patterns for events schema versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-17-andrea-magnorsky-introducing&quot;&gt;#18 - Andrea Magnorsky: Introducing Bytesize Architecture Sessions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-18-laila-bougria-debug-your&quot;&gt;#19 - Laïla Bougriâ: Debug your thinking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;#20 - Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;#21 - Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-22-on-performance-testing/&quot;&gt;#22 - On Performance Testing with Jarosław Pałka&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;#23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/frontent-architecture-backend-architecture/&quot;&gt;#24 - Frontent Architecture, Backend Architecture or just Architecture? With Tomasz Ducin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/applying-observability-from-strategy/&quot;&gt;#25 - Applying Observability: From Strategy to Practice with Hazel Weakly&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/react-query-a-solution-for-frontend/&quot;&gt;#26 - React Query: A solution for Frontend State Management challenges? With Tomasz Ducin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/documenting-event-driven-architecture/&quot;&gt;#27 - Documenting Event-Driven Architecture with EventCatalog and David Boyne&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/practical-introduction-to-event-sourcing/&quot;&gt;#28 - Practical Introduction to Event Sourcing with Emmett&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also made them available on my YouTube Channel: &lt;a href=&quot;https://www.youtube.com/@event-driven&quot;&gt;https://www.youtube.com/@event-driven&lt;/a&gt;. So click subscribe, bell and all that stuff!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I believe that all of that gives you more content than a lot of books or paid online courses.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Why did I do it? Am I mad? What’s the catch?&lt;/p&gt;
&lt;p&gt;In past years, I delivered many unique materials. I also increased the number of subscribers, both paid and regular ones. Preparing the type of content that I try to deliver each week quality content, takes time and focus.&lt;/p&gt;
&lt;p&gt;I still want to do that and produce new articles, but I decrease the pressure to do full-length and deep dives each week—sometimes even on weekends. And I’m not sure how about you, but I prefer to spend weekends with my family.&lt;/p&gt;
&lt;p&gt;The number of paid subscribers grew, but still insufficient to fully focus on Architecture Weekly. That’s fine, as I have other work, such as &lt;a href=&quot;/en/training/&quot;&gt;consultancy and training&lt;/a&gt;, &lt;a href=&quot;https://github.com/event-driven-io/&quot;&gt;open source work&lt;/a&gt;, and contracts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I believe that making the Architecture Weekly free newsletter should free some time for me, reduce the pressure I put on myself and let me focus on other stuff.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I still plan to deliver similar content as I did, keeping the weekly cadence, maybe in a slightly different form, or changing it to every other week later on. We’ll see.&lt;/p&gt;
&lt;p&gt;So if you worry, don’t. I don’t plan to shut it down, but do it more for fun than profit. Quality should still be here, but maybe a bit lightweight form.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I would appreciate it if you shared the news with your friends, so they could also benefit from those free materials. I believe they’re worth it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What are your thoughts?&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Practical Introduction to Event Sourcing with Emmett]]></title><description><![CDATA[Titles can be misleading. No special guest this time, but I’d like to introduce you to Emmett. Oh well, most importantly, I’d like to…]]></description><link>https://event-driven.io/en/practical-introduction-to-event-sourcing/</link><guid isPermaLink="false">https://event-driven.io/en/practical-introduction-to-event-sourcing/</guid><content:encoded>&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Webinar recording&quot; src=&quot;https://www.youtube-nocookie.com/embed/SDXdcymKv-8?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Titles can be misleading. No special guest this time, but I’d like to introduce you to Emmett.&lt;/strong&gt; Oh well, most importantly, I’d like to &lt;strong&gt;introduce you to Event Sourcing and show you how straightforward it can be.&lt;/strong&gt; I believe that TypeScript, Node.js, and Emmett can help with that.&lt;/p&gt;
&lt;p&gt;For many years, I’ve tried convincing others that Event Sourcing is not so scary and quite useful. I truly believe in that, yet…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I see a chasm between its initial simplicity and its use in production. In our past webinar,&lt;/strong&gt; &lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;Alexey Zimarev said we need Event Sourcing frameworks.&lt;/a&gt; After thinking about it, I agree with that take. We’re not yet in the adoption phase to not need a framework or library.&lt;/p&gt;
&lt;p&gt;Over a year ago I started packing my Node.js samples into the package, which resulted in…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt;: a framework that will take your applications back to the future.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The idea behind Emmett was to make it easier to create business-focused applications and cut unnecessary code without using magic. The combination of using events and their business focus, the expressiveness of TypeScript and the lightness of Node.js is supposed to help with that.&lt;/p&gt;
&lt;p&gt;During the webinar I showed step by step:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;What’s Event Sourcing,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How to model and tell the story about your business process using events,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How to reflect that in the code, so modelling events, command, business logic,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How to test Event Sourcing business logic with Behaviour-Driven Design,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Joining that into REST WebApi,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;using PostgreSQL based storage,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;build read models.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of that is in a 1.5-hour webinar. So fasten your seat belts, watch the recording and tell me what you think.&lt;/p&gt;
&lt;p&gt;See the full source code for what we did during the webinar: &lt;a href=&quot;https://github.com/event-driven-io/emmett-starter-postgresql&quot;&gt;https://github.com/event-driven-io/emmett-starter-postgresql&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is open content with no paid wall, so I’d appreciate resharing it with your friends!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you want to learn more about Event Sourcing and ramp up your journey, &lt;strong&gt;I’ll be doing an open workshop with Avanscoperta on April 3rd—4th, 7th—8th, 2025. You can sign up too! Sign up here: &lt;a href=&quot;https://www.avanscoperta.it/en/training/practical-introduction-to-event-sourcing-workshop/?utm_source=architecture_weekly&amp;#x26;utm_medium=email&amp;#x26;utm_campaign=oskar_dudycz&quot;&gt;https://www.avanscoperta.it/en/training/practical-introduction-to-event-sourcing-workshop/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We’ll do learning in a similar hands-on spirit!&lt;/p&gt;
&lt;p&gt;Check more about Emmett in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett documentation&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Emmett Discord Channel&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/introducing_emmett/&quot;&gt;Announcing Emmett! Take your event-driven applications back to the future!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/emmett_postgresql_event_store/&quot;&gt;Event Sourcing on PostgreSQL in Node.js just became possible with Emmett&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/emmett_projections_testing/&quot;&gt;Writing and testing event-driven projections with Emmett, Pongo and PostgreSQL&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/idempotent_command_handling/&quot;&gt;Idempotent Command Handling&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/@mbneto/event-sourcing-with-emmett-how-to-reduce-the-entry-barrier-bf2d638c0437&quot;&gt;Event Sourcing with Emmett: Reducing the Entry Barrier&lt;/a&gt; by Mário Bittencourt&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/testing_event_sourcing_emmett_edition/&quot;&gt;Testing Event Sourcing, Emmett edition&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/projections_and_event_metadata/&quot;&gt;Using event metadata in event-driven projections&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/custom_test_container_on_esdb_example/&quot;&gt;How to configure a custom Test Container on the EventStoreDB example&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;other-webinars&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#other-webinars&quot; aria-label=&quot;other webinars permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Other Webinars&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing&quot;&gt;#1 - From CRUD to Event Sourcing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short&quot;&gt;#2 - Keep your streams short! Or how to model Event-Sourced systems efficiently&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;#3 - Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice&quot;&gt;#4 - From CRUD to CQRS in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100&quot;&gt;#5 - Architecture Weekly 100 Edition - Live Q&amp;#x26;A&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;#6 - Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven&quot;&gt;#7 - Design and test Event-Driven projections and read models&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates&quot;&gt;#8 - Slim down your aggregates!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;#9 - Radek Maziarka - Modularization with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;#10 - PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski&quot;&gt;#11 - Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify&quot;&gt;#12 - Jeremy D. Miller: Simplify your architecture with Wolverine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic&quot;&gt;#13 - Yves Goeleven - The Fantastic 9&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-14-mateusz-jendza-why-verified&quot;&gt;#14 - Mateusz Jendza - Why Verified Credentials is the Future of Digital Identity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-15-mario-bittencourt-leveraging&quot;&gt;#15 - Mário Bittencourt: Leveraging BPMN for Seamless Team Collaboration in Software Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-1-sagas-hector-garcia&quot;&gt;#16 - Papers We Love #1 - Sagas (Hector Garcia-Molina, Kenneth Salem)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-16-simple-patterns-for-events&quot;&gt;#17 - Simple patterns for events schema versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-17-andrea-magnorsky-introducing&quot;&gt;#18 - Andrea Magnorsky: Introducing Bytesize Architecture Sessions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-18-laila-bougria-debug-your&quot;&gt;#19 - Laïla Bougriâ: Debug your thinking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;#20 - Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;#21 - Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-22-on-performance-testing/&quot;&gt;#22 - On Performance Testing with Jarosław Pałka&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;#23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/frontent-architecture-backend-architecture/&quot;&gt;#24 - Frontent Architecture, Backend Architecture or just Architecture? With Tomasz Ducin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/applying-observability-from-strategy/&quot;&gt;#25 - Applying Observability: From Strategy to Practice with Hazel Weakly&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/react-query-a-solution-for-frontend/&quot;&gt;#26 - React Query: A solution for Frontend State Management challenges? With Tomasz Ducin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/documenting-event-driven-architecture/&quot;&gt;#27 - Documenting Event-Driven Architecture with EventCatalog and David Boyne&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;#28 - Practical Introduction to Event Sourcing with Emmett&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[So you want to break down monolith? Read that first.]]></title><description><![CDATA[I’ve been involved in several projects that tried to break down a legacy monolith. Some were successful, but most were not. Even those…]]></description><link>https://event-driven.io/en/so-you-want-to-break-down-monolith/</link><guid isPermaLink="false">https://event-driven.io/en/so-you-want-to-break-down-monolith/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;I’ve been involved in several projects that tried to break down a legacy monolith.&lt;/strong&gt; Some were successful, but most were not. Even those moderately successful had a common, painful experience. I burned my fingers and saw other teams struggle with this challenge. Not because they lacked skills, but often because their approach was too ambitious.&lt;/p&gt;
&lt;p&gt;After recently discussing monolith-to-microservices migrations, I decided to let you learn from my and other people’s mistakes. And spare you some &lt;em&gt;unwanted&lt;/em&gt; fun.&lt;/p&gt;
&lt;p&gt;Let me share what I’ve seen work and learned. That’s my experience, so filter that through yours, but I hope this will be a good food for thoughts.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d71c9c4f8d9f329b317f93c507251a35/38b44/2025-03-03-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 81%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAQABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwQA/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEAMQAAABjuNSbHj/xAAaEAACAwEBAAAAAAAAAAAAAAABAgADERMh/9oACAEBAAEFAuY0VlgZW6KXu8wT/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGhAAAgIDAAAAAAAAAAAAAAAAAAEQESIxUf/aAAgBAQAGPwI41DTqzDcf/8QAHBAAAgICAwAAAAAAAAAAAAAAAAERMSFBYXGR/9oACAEBAAE/IXVW6FmVUMHB3sQjFo24Ei48P//aAAwDAQACAAMAAAAQow//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAbEAEBAQEBAAMAAAAAAAAAAAABESEAMUFRYf/aAAgBAQABPxDRddWrtzhqxRRb+v1yLBpnvE2kC0iu04lNlMUHAyj6vz7/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d71c9c4f8d9f329b317f93c507251a35/507e6/2025-03-03-cover.webp 200w,
/static/d71c9c4f8d9f329b317f93c507251a35/28a80/2025-03-03-cover.webp 400w,
/static/d71c9c4f8d9f329b317f93c507251a35/8d2ea/2025-03-03-cover.webp 800w,
/static/d71c9c4f8d9f329b317f93c507251a35/68fc1/2025-03-03-cover.webp 1200w,
/static/d71c9c4f8d9f329b317f93c507251a35/4b075/2025-03-03-cover.webp 1280w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d71c9c4f8d9f329b317f93c507251a35/37402/2025-03-03-cover.jpg 200w,
/static/d71c9c4f8d9f329b317f93c507251a35/4cda9/2025-03-03-cover.jpg 400w,
/static/d71c9c4f8d9f329b317f93c507251a35/c60e9/2025-03-03-cover.jpg 800w,
/static/d71c9c4f8d9f329b317f93c507251a35/6c738/2025-03-03-cover.jpg 1200w,
/static/d71c9c4f8d9f329b317f93c507251a35/38b44/2025-03-03-cover.jpg 1280w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d71c9c4f8d9f329b317f93c507251a35/c60e9/2025-03-03-cover.jpg&quot;
            alt=&quot;undefined&quot;
            title=&quot;undefined&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://en.wikipedia.org/wiki/List%5C_of%5C_largest%5C_monoliths&quot;&gt;https://en.wikipedia.org/wiki/List\_of\_largest\_monoliths&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;be-realistic-about-what-stays-in-the-monolith&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#be-realistic-about-what-stays-in-the-monolith&quot; aria-label=&quot;be realistic about what stays in the monolith permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Be Realistic About What Stays in the Monolith&lt;/h2&gt;
&lt;p&gt;Yes, that’s the title, and it is not a mistake. If you believe you’ll be able to rewrite everything, I’m sorry, but you’re probably wrong. I was wrong like that multiple times.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s anecdotal evidence, but I haven’t seen a complete monolith migration succeed.&lt;/strong&gt; Most were left in the middle of transition after going far above the planned time for rewrite.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some finished the migration, but replaced the old legacy with the new legacy after rushing the deadline.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Some didn’t even manage those phases, as they sunk product and budget and were thrown away.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, the safe assumption is that some part of the old system will remain and continue running.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And that’s perfectly fine. The goal shouldn’t be completely eliminating the monolith, but extracting the parts that benefit from independence.&lt;/p&gt;
&lt;p&gt;Consider a video editing suite with project management, rendering pipeline, effects processing, and asset management. After extracting the performance-critical rendering pipeline and the frequently updated effects processing, you might find the core project management and stable asset management work perfectly fine in the original monolith. Keeping them there could save significant effort without compromising the migration’s benefits.&lt;/p&gt;
&lt;h3 id=&quot;when-to-keep-functionality-in-the-monolith&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#when-to-keep-functionality-in-the-monolith&quot; aria-label=&quot;when to keep functionality in the monolith permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;When to Keep Functionality in the Monolith&lt;/h3&gt;
&lt;p&gt;Sometimes it makes sense to keep certain functionality in the monolith when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It rarely changes&lt;/strong&gt; (like so authentication logic that follows well-established standards)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I&lt;strong&gt;t’s deeply connected to other monolith components&lt;/strong&gt; (like a complex pricing engine with many internal dependencies)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The extraction cost exceeds the benefits&lt;/strong&gt; (like a reporting module used by only a few internal users)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The team lacks experience with distributed systems&lt;/strong&gt; (like a team of domain experts who primarily work with business logic)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A financial system might benefit from extracting the high-volume transaction processing and notification services while keeping the complex compliance and audit engines in the monolith because they are computationally intensive and tightly coupled.&lt;/p&gt;
&lt;h2 id=&quot;focus-on-business-value-first&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#focus-on-business-value-first&quot; aria-label=&quot;focus on business value first permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Focus on Business Value First&lt;/h2&gt;
&lt;p&gt;I think that when planning migrations, we should start by talking with product people to identify which parts of the system deliver the most value or cause the most pain. We should &lt;a href=&quot;/en/follow_the_money/&quot;&gt;follow the money&lt;/a&gt;. As cynical as it may seem, finding out who’ll be paying us money is as helpful. That’s critical in selecting the right tradeoffs. Example?&lt;/p&gt;
&lt;p&gt;Let’s say that we’re maintaining a SaaS platform like Shopify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Who’s our customer?&lt;/strong&gt; A shop that’ll be paying for our subscriptions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Who will use our platform the most?&lt;/strong&gt; People who will buy goods in online shops. Will they be paying us? No, our &lt;strong&gt;customer&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Who should we optimise for?&lt;/strong&gt; Our clients, so subscription payers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We should ask ourselves questions when discussing our features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When will our customer be sad?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When will our user be sad?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Will the fact that our user is sad eventually make our customer sad?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Will that be more disappointment, sadness or despair?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, we should try to make both users and customers happy. Still, we need to fulfil the needs of our customers first, then do that for other users. That may sound cynical, but it’s not. If we don’t satisfy our paying customers, then we won’t make our work sustainable, and other users will eventually be also harmed by that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So I suggest focusing on business value for our customers and thus the money-flow for us:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Pick a specific functionality that gives clear business value&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Get it to production relatively quickly&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Learn from that real-world experience&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adjust further plans based on what they learned&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, consider a team initially planning to extract the user management component because it seems technically straightforward. However, after discussions with the business, they might discover that improving the order processing system would deliver more immediate value to customers experiencing delays. By pivoting to that area, they could show benefits much faster and build momentum for the broader migration.&lt;/p&gt;
&lt;p&gt;This iterative approach works better than those “grand migration” plans that extend for years without delivering interim value.&lt;/p&gt;
&lt;h2 id=&quot;define-specific-measurable-metrics-not-just-goals&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#define-specific-measurable-metrics-not-just-goals&quot; aria-label=&quot;define specific measurable metrics not just goals permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Define Specific, Measurable Metrics (Not Just Goals)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Teams that succeed in migrations typically align with business stakeholders on specific, measurable metrics before starting - not just vague goals.&lt;/strong&gt; There’s a critical difference here that’s often overlooked.&lt;/p&gt;
&lt;p&gt;For instance, teams might start with business goals like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For a game development studio, improving the player matchmaking experience&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;For a video streaming platform like Netflix, handling traffic spikes during new releases&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;For a photo sharing app like Instagram, enhancing content moderation effectiveness&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;But these aren’t measurable metrics - they’re aspirational goals.&lt;/strong&gt; To drive effective decision-making, you need to translate these into concrete, measurable metrics:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Instead of &lt;em&gt;“improving matchmaking experience”&lt;/em&gt;, use: &lt;em&gt;&lt;strong&gt;“Reduce matchmaking wait time from 45 seconds to under 15 seconds”&lt;/strong&gt;&lt;/em&gt; and &lt;em&gt;&lt;strong&gt;“Increase matchmaking accuracy (players of similar skill) by 20%“,&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Instead of &lt;em&gt;“handling traffic spikes”&lt;/em&gt;, use: &lt;em&gt;”&lt;strong&gt;Maintain 99.99% availability during 3x normal traffic events”&lt;/strong&gt;&lt;/em&gt; and &lt;em&gt;&lt;strong&gt;“Keep response time under 200ms during peak load”&lt;/strong&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Instead of &lt;em&gt;“enhancing content moderation”&lt;/em&gt;, use: &lt;em&gt;&lt;strong&gt;“Reduce time-to-detection for policy violations from 30 minutes to under 5 minutes”&lt;/strong&gt;&lt;/em&gt; and “Decrease false positive rate from 8% to under 3%“.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;connect-metrics-to-business-impact---with-evidence&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#connect-metrics-to-business-impact---with-evidence&quot; aria-label=&quot;connect metrics to business impact   with evidence permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Connect Metrics to Business Impact - With Evidence&lt;/h3&gt;
&lt;p&gt;Even more important: for each metric, you must clearly articulate WHY achieving this target matters to the business. Without this connection, you’re just chasing numbers for their own sake.&lt;/p&gt;
&lt;p&gt;But be careful - don’t fall into the trap of making up impressive-sounding impact estimates without solid evidence. I’ve seen too many people come up with statements like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Reducing matchmaking wait time to under 15 seconds will increase player retention by an estimated 18%, translating to approximately $0.5M in additional annual revenue”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;While this sounds convincing, if you can’t back it up with real data, it’s just a guess that will eventually undermine your credibility.&lt;/p&gt;
&lt;p&gt;Instead, ground your impact in one of these sources (in order of preference):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Actual measurements from your current system&lt;/strong&gt;&lt;br&gt;
“Our current data shows that for every 10-second reduction in matchmaking time, we see a 7% decrease in session abandonment”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Results from similar changes in your organization&lt;/strong&gt;&lt;br&gt;
“When we improved checkout speed by 30% last year, conversion rates improved by 12%”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Industry benchmarks from credible sources:&lt;/strong&gt;&lt;br&gt;
“According to industry research, streaming platforms typically lose 5.8% of viewers for each additional second of buffering time”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Small-scale experiments&lt;/strong&gt;&lt;br&gt;
“In a limited test with 5% of users, we saw a 9% increase in engagement when content recommendations were personalized”&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you don’t have any of these, be honest about it and start smaller:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“We currently lack data on the exact impact of moderation speed, so we propose starting with a small extraction to gather this data before committing to a full migration”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We also discussed the importance of business metrics with Gojko Adzic in:&lt;/p&gt;
&lt;p&gt;[&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 56px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC80lEQVR42o2VO0hbYRTHr4nvJ6VK1fiig7YIobo4ZDAWEgfpIg4+0aWDQ8DFki0FFReLLipOIopgfEDRRR0FBxdRR4cKVXAQI4nGmHu9/55zki9VWmM+OPnuvd/5fuf/Pc6JBsCiUbu8vHz38PDw1TAMbzQaHab+WzIjH9UPB4NBBzP8fr+Vey0UCtlp8BdizUTqzVT+JOa7phrB9uMOETI9HA7r3CezSCSinqOPj48GTyZhn7Wbm5v3BAyqiB6PBwMDA/KytbWFi4uL2IAZE06+0i8uLsLpdOLq6gpxMO7v739o1NfRPoT4w8bGhkmC0d3dLZOampqwvb39DKTregLIvkNDQzLMP7e3tzMaSa+j9d/xh/HxcQH29/fLpMHBQezvx3aDovM+4e5OXLGwsCBAl8uVUEhb9X9gb2/vM1VPm1I4Pz8vwLa2tpeBo6OjZnp6OmpqatDZ2SlgVsvPHR0d8tzX1ye93W5HdnY2WltbXwaOjIyYmZmZKC8vF2c2VsEB6uvrYbFYwONZWVkoLi5GQUEB3G53cmBGRgYqKytRXV2NoqIiTE5O4vT0FIeHh9jd3UVFRYUY++Tn578OZAUMy8nJQVdXF46Pj9HY2Ija2losLy9jenoavC1VVVWpA3mJvNTZ2Vl4vV6MjY3h6OgIDocDe3t7slRW+SqQD+Wpwp6eHlkqHwArXFpawtzcHHhblMJXD4UPgqOrg5mYmMDJyYko29nZQWlpKUpKSmCz2URp0ns4NTUl95CjM1Atq6GhAc3NzQIoKysTGB8K72V7e/s/wA90gSX1rq+vzZaWFlHGILbCwkJYrVakpaXJO0Pz8vKQm5srgQ8ODhKpR6wZjZLbRtUioIoDFQtsbm5iZWUFq6urYmtra1hfX0+8Kzs7O1MZpceBPlW+/PyBwDpVFSMeMSUz4vlJc8ME/ChAUvWGKs5P42/yplpkTS5rNO035fgXgfl8Pgv/DbAFAoFPNMAXy5WiuakKOc/Pz9+qv4A/x/GAJi+lEOYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/61627/image-2.webp 56w&quot;
              sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png 56w&quot;
            sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;Architecture Weekly&lt;/p&gt;
&lt;p&gt;Webinar #23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/p&gt;
&lt;p&gt;Welcome to the next week…&lt;/p&gt;
&lt;p&gt;Listen now&lt;/p&gt;
&lt;p&gt;2 years ago · 4 likes · Oskar Dudycz and Gojko Adzic&lt;/p&gt;
&lt;p&gt;](/en/webinar-23-gojko-adzic-on-designing/)&lt;/p&gt;
&lt;h3 id=&quot;be-skeptical-of-customer-feedback&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#be-skeptical-of-customer-feedback&quot; aria-label=&quot;be skeptical of customer feedback permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Be Skeptical of Customer Feedback&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Another pitfall: don’t rely solely on customer interviews or surveys to justify your metrics.&lt;/strong&gt; People are naturally polite and will often express interest in features they’ll never actually use. I’ve witnessed countless projects justified by enthusiastic customer feedback that resulted in features nobody used.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let me share a personal experience that illustrates this issue. I was once working on rebuilding a legacy document management system.&lt;/strong&gt; The original system used on-premise custom storage. It was a decent implementation in its heydays, but it required modernisation and integration with new cloud features. The product team wanted to allow attach multiple cloud file storage. One of it was SharePoint Online integration, which would allow users to connect our system to their files and manage them.&lt;/p&gt;
&lt;p&gt;As much as I hate SharePoint, that made me suspicious. I was curious how many users would like to pay additional money for it, as SharePoint already handles some of the flow mechanisms.&lt;/p&gt;
&lt;p&gt;When I met with the Product Owner, I asked some basic questions:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Did you ask users if they actually want a new UI for SharePoint?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“SharePoint’s API requires granting our application admin permissions in their tenant - did you check if enterprise customers are okay with that security requirement? Enterprises are usually mad about that stuff.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The answer to the first question was, “Yes, they said they like it” - which already raised a red flag for me.&lt;/p&gt;
&lt;p&gt;Saying they “like” something in a conversation differs greatly from committing to use it. Thus I also asked if customers would actually use this feature and pay for it.&lt;/p&gt;
&lt;p&gt;The uncomfortable silence that followed told me everything I needed to know. The team had taken polite interest as validation and was about to start on a significant technical effort without confirming if anyone would actually use the feature, let alone pay for it.&lt;/p&gt;
&lt;p&gt;I’ve seen this pattern repeatedly. Teams confuse politeness for validation and interest for commitment. Building features based on what users say rather than what they do is a recipe for wasted effort.&lt;/p&gt;
&lt;p&gt;Instead:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Look at what users actually do, not what they say.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Pay attention to patterns of behavior, not isolated requests.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Watch for problems users work around rather than features they ask for.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When possible, observe users in their natural environment rather than asking hypothetical questions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;question-initiatives-without-clear-value&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#question-initiatives-without-clear-value&quot; aria-label=&quot;question initiatives without clear value permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Question Initiatives Without Clear Value&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;This step also serves as a crucial filter.&lt;/strong&gt; If you can’t connect a potential service extraction to a measurable metric with concrete, evidence-based business value, you should seriously question whether it’s worth doing at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’ve seen teams abandon their migration plans after this exercise.&lt;/strong&gt; And that’s great, as if you reveal minimal business benefits compared to the required engineering effort, then why take that? It may be better to redirect those resources to changes with clearer value propositions—a much better outcome than pursuing technical changes for their own sake.&lt;/p&gt;
&lt;p&gt;If you’re uncertain about the value, start much smaller:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Extract a minimal component first.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Measure its actual impact.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use that real data to decide whether to continue with larger extractions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What makes good metrics for migrations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Specific and measurable&lt;/strong&gt;: You can collect data and show a clear before/after comparison.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Directly tied to business value&lt;/strong&gt;: They represent outcomes customers care about, not just technical improvements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Time-bound&lt;/strong&gt;: Include target dates for achieving improvements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Baseline-aware&lt;/strong&gt;: Start by measuring current performance to demonstrate improvement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Evidence-based&lt;/strong&gt;: Backed by data, not just estimates or hopes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What to avoid:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Technical metrics without business context&lt;/strong&gt;: “Increasing service count” or “reducing code lines per service” aren’t valuable by themselves&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Vague improvements&lt;/strong&gt;: “Better scalability” or “improved resilience” without defining what these mean numerically&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Developer-only concerns&lt;/strong&gt;: Metrics that don’t connect to user or business outcomes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Unmeasurable goals&lt;/strong&gt;: If you can’t collect the data, it’s not a useful metric&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wishful projections&lt;/strong&gt;: Making up business impact numbers without supporting evidence&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Having concrete, evidence-based metrics helps maintain business support when challenges arise. Without clear, measurable targets grounded in reality, support will vanish when the migration takes longer than expected or doesn’t deliver the imagined benefits.&lt;/p&gt;
&lt;p&gt;So instead of suggesting &lt;em&gt;“improving system maintainability”&lt;/em&gt; as the goal, translate this to: &lt;em&gt;“Reduce average feature delivery frequency from 3 weeks to 1 week”&lt;/em&gt; and &lt;em&gt;“Reduce production incidents by 40% year-over-year.”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That’s already better, but not perfect. If you did something like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“In our last quarterly developer survey, 72% of time was spent dealing with cross-cutting concerns in the monolith. Based on this, faster feature delivery will allow us to ship our top-requested features within 10 days instead of 30, and our customer success team reports that 64% of account cancellations mention reliability issues, which we expect to address with this work.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then you’re talking. Connecting these technical challenges to concrete, measurable business outcomes is key.&lt;/p&gt;
&lt;p&gt;This clarity - based on actual measurements rather than wishful thinking - was what ultimately secured executive support throughout a challenging migration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And yes, that requires preparation, and there’s a risk. The risk is that when you find evidence, it will be evidence that you should not do that at all.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;extraction-approaches-that-work&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#extraction-approaches-that-work&quot; aria-label=&quot;extraction approaches that work permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Extraction Approaches That Work&lt;/h2&gt;
&lt;p&gt;Based on what I’ve seen in various migrations, here’s an approach that tends to be effective. Each step addresses specific risks that often derail migration projects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Define clear boundaries first&lt;/strong&gt;: Before extracting code, understand where the service begins and ends, what data it owns, and what APIs it would expose. This clarity prevents scope creep and helps identify hidden dependencies early. Too many teams jump into coding without this boundary work and end up with a distributed monolith rather than true microservices.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use feature flags to control the transition&lt;/strong&gt;: Deploy new services but keep the old code running, using feature flags to direct traffic gradually. This gives you an escape hatch when issues arise and lets you perform controlled experiments with real traffic. Without feature flags, you’re forced into risky big-bang cutover scenarios.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Start with read operations&lt;/strong&gt;: Read operations are safer to migrate first because they don’t modify state. Get reads working perfectly before attempting to migrate writes. This approach isolates complexity and reduces the surface area for potential data inconsistencies, which are among the hardest problems to debug.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Monitor extensively&lt;/strong&gt;: Compare responses between old and new systems and log differences to catch problems early. Data discrepancies often appear subtly at first before cascading into significant issues. Many teams neglect this step and end up with silent inconsistencies that undermine confidence in the migration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Migrate in slices&lt;/strong&gt;: Rather than migrating entire services at once, take small vertical slices of functionality and migrate them incrementally. This keeps your changes bounded and easier to reason about. It also delivers value sooner and provides learning opportunities that inform subsequent slices.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach works because it acknowledges uncertainty. Rather than assuming perfect knowledge upfront, it creates tight feedback loops at each step, allowing teams to adapt their strategy based on what they learn. It also maintains business continuity throughout the migration, which builds stakeholder confidence.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It also gives you the chance to stop migration at a specific step and validate whether you really need to take the next steps.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-strangler-fig-pattern-a-practical-approach&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-strangler-fig-pattern-a-practical-approach&quot; aria-label=&quot;the strangler fig pattern a practical approach permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Strangler Fig Pattern: A Practical Approach&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://martinfowler.com/bliki/StranglerFigApplication.html&quot;&gt;Strangler Fig pattern&lt;/a&gt; offers a robust framework for incremental migration. The name comes from how strangler fig vines gradually grow around a host tree, eventually replacing it entirely.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4b75f44e9e9eef591ae8c28028281e3e/8537d/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACQ0lEQVR42jVS3U8TQRC/f0j/EF/UyIO+GiMaE0FNjA8K6oNKeEE0AipNgJSaGLA2xaZSURAEhCKlVtq77vVa79rrtb3r3e3XXdvUKY2T7M7Hb3ZmdmYEjLHj2q7jnlCP2Y6NMQEZqG+lhPVRx3UY5R5r9e0C6Jz6EIEQwogHJp93LKcOToxwiuGZU6kXQCaEgmeulPyFVgkmkEDg1EuhjfHw5an4Tbn8m1K2tDX5dPlSYO1uUc+hSurt2vCzjxeCmyOGqR2Xtmdid8YjF6MHz22nKdRM/WXs6uj8wOD0qfDO5E428mT57IOF89dfn57/+vDN6u3H7889ejdwf/FMCm3OJUYnV65Mfxqeig1V6kVB1sSx2cFXwaGZ2L1KVfuwEZiYuzERujYbH1FU9CUZDq2MvVi8tZX6DN/5qxcSu8G99GqtYfTKZtwrF5X1aIgyDr1qtbs7iUh6/ztlHnFJt9vNJrdR5gBQx7E59y3DMKsVyhi0UwBWVgs/N2Lc9y2zwf3W7rcoyh4RxqymiSkRM/uqImFCbNt2sFtRZa0kORhDIwXm+dKfw/hSwGt3mpbZ6XbXIgtHe+t+u23bTa/Vzh7+KIhpKBAyw22UlaqmQF9hcAKhtKypUu4YJgGxq1Ujm0nnxVyj0YDCjFpNEnMFGZmmCRuh69W8JGqa1l8BgRBsGDVVVQGDVAihfB7JsqypKsQuFYuSJKE80nUdHIqKIokSoJZlgSpAAGCUkP7ScM4p1MTgoj07iOzkUAoo+0+492f3H9SlNcTbPF+mAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/4b75f44e9e9eef591ae8c28028281e3e/507e6/image-3.webp 200w,
/static/4b75f44e9e9eef591ae8c28028281e3e/28a80/image-3.webp 400w,
/static/4b75f44e9e9eef591ae8c28028281e3e/8d2ea/image-3.webp 800w,
/static/4b75f44e9e9eef591ae8c28028281e3e/68fc1/image-3.webp 1200w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/4b75f44e9e9eef591ae8c28028281e3e/36ca5/image-3.png 200w,
/static/4b75f44e9e9eef591ae8c28028281e3e/a3397/image-3.png 400w,
/static/4b75f44e9e9eef591ae8c28028281e3e/a331c/image-3.png 800w,
/static/4b75f44e9e9eef591ae8c28028281e3e/8537d/image-3.png 1200w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/4b75f44e9e9eef591ae8c28028281e3e/a331c/image-3.png&quot;
            alt=&quot;What is the Strangler Fig Pattern? | by Denny Lesmana | Medium&quot;
            title=&quot;What is the Strangler Fig Pattern? | by Denny Lesmana | Medium&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://dennylesmana.medium.com/what-is-the-strangler-fig-pattern-1560443b8459&quot;&gt;https://dennylesmana.medium.com/what-is-the-strangler-fig-pattern-1560443b8459&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What makes this pattern especially valuable is how it manages risk. Instead of a risky “big bang” rewrite, you create a facade in front of the legacy system and gradually reroute functionality through that facade to new services. The old and new systems coexist until the migration is complete, at this point, the old system can be decommissioned.&lt;/p&gt;
&lt;p&gt;The key aspects that make the Strangler Fig pattern worth considering:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Risk reduction&lt;/strong&gt;: You’re never replacing the entire system at once, so each change has limited impact&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Incremental value delivery&lt;/strong&gt;: New functionality becomes available as soon as each piece is ready&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reversibility&lt;/strong&gt;: If a newly extracted component doesn’t perform well, you can easily route traffic back to the original implementation&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Coexistence&lt;/strong&gt;: Legacy and new components operate side-by-side, allowing for gradual migration at a pace that makes sense for your organization&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Following the Strangler Fig approach when extracting a payment processing service from an e-commerce platform might look like this:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Build an API facade in front of the monolith’s payment code&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Implement the new payment service behind this facade&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Gradually shift traffic from the monolith to the new service, starting with low-value test transactions&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Monitor closely for discrepancies in payment processing and reconciliation&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Deploying to production incrementally is crucial. Just doing this work in development environments doesn’t give you the feedback you need about real-world behavior, especially around performance and reliability.&lt;/p&gt;
&lt;p&gt;The Strangler Fig pattern is one of the few approaches I’ve consistently seen work in practice. It acknowledges that migrations take time and provides a structure for managing that time effectively, while continuously delivering value throughout the process.&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://martinfowler.com/bliki/StranglerFigApplication.html&quot;&gt;Martin Fowler - Strangler Fig&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://shopify.engineering/refactoring-legacy-code-strangler-fig-pattern&quot;&gt;Shopify - Refactoring Legacy Code with the Strangler Fig Pattern&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;my-take-on-database-level-migration&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#my-take-on-database-level-migration&quot; aria-label=&quot;my take on database level migration permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;My Take on Database-Level Migration&lt;/h2&gt;
&lt;p&gt;I’ve seen teams reach for tools like &lt;a href=&quot;https://debezium.io/&quot;&gt;Debezium&lt;/a&gt; to automatically synchronize data between old and new systems. While this approach has its place, I’ve generally found it problematic.&lt;/p&gt;
&lt;p&gt;Legacy databases are rarely clean. The data is typically mixed together in ways that don’t reflect clear domain boundaries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When simply replicating this data, teams often copy the problems rather than solve them.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In legacy systems, data are often entangled in a single database. Looking at the table structures, you won’t easily see business context, if at all. Trying to replicate data structures 1:1 to new services would preserve these poor boundaries, creating distributed copies of a tangled data model.&lt;/p&gt;
&lt;p&gt;Instead, this approach often works better:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add event publishing directly to the legacy application for relevant business events.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Have new services consume these events.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build optimized data stores for the new services.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This provides cleaner boundaries and better control, though it requires more initial development work. Still, that creates other challenges.&lt;/p&gt;
&lt;h2 id=&quot;the-hybrid-state-is-challenging&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-hybrid-state-is-challenging&quot; aria-label=&quot;the hybrid state is challenging permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Hybrid State is Challenging&lt;/h2&gt;
&lt;p&gt;One of the challenging aspects of doing Strangler Fig-like migration is maintaining data consistency. You’ll have two places where the same data should be kept. You need to maintain two codebases, keep data synchronized, and ensure consistency across systems.&lt;/p&gt;
&lt;p&gt;Of course there are strategies to deal with that, like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dual writes&lt;/strong&gt;: For simple cases, having the application write to both old and new systems can work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Event-based synchronization&lt;/strong&gt;: For more complex scenarios, publishing events when data changes and having both systems consume these events proves more reliable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reconciliation jobs&lt;/strong&gt;: Regular jobs that compare data between systems and fix inconsistencies serve as a safety net.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Still, hybrid mode is a big challenge.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For instance, when extracting a customer profile service from a larger CRM system, sales teams would continue creating and updating customer records in the legacy system during migration, which would all need to appear correctly in the new service. Meanwhile, data enrichment processes might be running in both systems, potentially creating conflicts.&lt;/p&gt;
&lt;p&gt;If we add to that that both teams may provide changes to the business logic when fine-tuning or &lt;em&gt;bug fixing&lt;/em&gt; the legacy system, then the complexity arises even more.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, the sooner you switch one system to be a source of truth and the other read-only, the less it will cost.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;team-structure-considerations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#team-structure-considerations&quot; aria-label=&quot;team structure considerations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Team Structure Considerations&lt;/h2&gt;
&lt;p&gt;Conway’s Law holds true - system architecture tends to reflect team structure. If you want independent services but maintain a single team responsible for everything, you’ll likely end up with tightly coupled services that behave more like a distributed monolith.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When planning a migration these organizational questions are as important as the technical ones:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Who will own each new service?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How will teams communicate during and after migration?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How will knowledge transfer happen?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reorganizing into domain-focused teams before beginning the technical migration can greatly improve results compared to keeping the original team structure.&lt;/p&gt;
&lt;p&gt;See also more in:&lt;/p&gt;
&lt;p&gt;[&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 56px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC80lEQVR42o2VO0hbYRTHr4nvJ6VK1fiig7YIobo4ZDAWEgfpIg4+0aWDQ8DFki0FFReLLipOIopgfEDRRR0FBxdRR4cKVXAQI4nGmHu9/55zki9VWmM+OPnuvd/5fuf/Pc6JBsCiUbu8vHz38PDw1TAMbzQaHab+WzIjH9UPB4NBBzP8fr+Vey0UCtlp8BdizUTqzVT+JOa7phrB9uMOETI9HA7r3CezSCSinqOPj48GTyZhn7Wbm5v3BAyqiB6PBwMDA/KytbWFi4uL2IAZE06+0i8uLsLpdOLq6gpxMO7v739o1NfRPoT4w8bGhkmC0d3dLZOampqwvb39DKTregLIvkNDQzLMP7e3tzMaSa+j9d/xh/HxcQH29/fLpMHBQezvx3aDovM+4e5OXLGwsCBAl8uVUEhb9X9gb2/vM1VPm1I4Pz8vwLa2tpeBo6OjZnp6OmpqatDZ2SlgVsvPHR0d8tzX1ye93W5HdnY2WltbXwaOjIyYmZmZKC8vF2c2VsEB6uvrYbFYwONZWVkoLi5GQUEB3G53cmBGRgYqKytRXV2NoqIiTE5O4vT0FIeHh9jd3UVFRYUY++Tn578OZAUMy8nJQVdXF46Pj9HY2Ija2losLy9jenoavC1VVVWpA3mJvNTZ2Vl4vV6MjY3h6OgIDocDe3t7slRW+SqQD+Wpwp6eHlkqHwArXFpawtzcHHhblMJXD4UPgqOrg5mYmMDJyYko29nZQWlpKUpKSmCz2URp0ns4NTUl95CjM1Atq6GhAc3NzQIoKysTGB8K72V7e/s/wA90gSX1rq+vzZaWFlHGILbCwkJYrVakpaXJO0Pz8vKQm5srgQ8ODhKpR6wZjZLbRtUioIoDFQtsbm5iZWUFq6urYmtra1hfX0+8Kzs7O1MZpceBPlW+/PyBwDpVFSMeMSUz4vlJc8ME/ChAUvWGKs5P42/yplpkTS5rNO035fgXgfl8Pgv/DbAFAoFPNMAXy5WiuakKOc/Pz9+qv4A/x/GAJi+lEOYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/61627/image-2.webp 56w&quot;
              sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png 56w&quot;
            sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;Architecture Weekly&lt;/p&gt;
&lt;p&gt;Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/p&gt;
&lt;p&gt;Hey! 😀…&lt;/p&gt;
&lt;p&gt;Listen now&lt;/p&gt;
&lt;p&gt;2 years ago · 6 likes · Oskar Dudycz&lt;/p&gt;
&lt;p&gt;](&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees?utm_source=substack&amp;#x26;utm_campaign=post_embed&amp;#x26;utm_medium=web&quot;&gt;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees?utm_source=substack&amp;#x26;utm_campaign=post_embed&amp;#x26;utm_medium=web&lt;/a&gt;)&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;After seeing many breaking-monoliths-migrations, I’ve noticed a pattern: the successful teams focus relentlessly on business value, not architectural purity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The most effective migrations I’ve witnessed weren’t driven by a desire to use the latest architectural patterns or to eliminate the monolith completely. They were driven by specific business needs: faster delivery of high-value features, better resilience in critical components, or enabling teams to work independently.&lt;/p&gt;
&lt;p&gt;In contrast, teams that started migrations because &lt;em&gt;“microservices are the future”&lt;/em&gt; or &lt;em&gt;“our monolith is a mess”&lt;/em&gt; often found themselves in trouble - with half-completed migrations, increased complexity, and diminishing business support.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The truth is that most systems benefit from a pragmatic mix of architectural styles.&lt;/strong&gt; Some components genuinely benefit from being extracted into independent services, while others work perfectly well in a monolith. Wisdom lies in knowing the difference.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you take only one thing from this article, let it be this: Don’t migrate to microservices because it’s fashionable.&lt;/strong&gt; Migrate specific components because doing so solves real problems that matter to your business. Keep your changes small, focused, and value-driven. Measure your progress not by how many services you’ve created, but by how well you’ve addressed the business needs that started this journey.&lt;/p&gt;
&lt;p&gt;Architecture should serve the business, not the other way around. Stay focused on that, and your migration has a much better chance of being among the successful ones.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write in the comments how your breaking the monolith actions went so far! What worked out and what did not?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;See also my other articles in that topic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/the_end_is_near_for_crud_data/&quot;&gt;Hitchhiker’s Guide To Moving From Relational Data To Events&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/strategy_on_migrating_relational_data_to_document_based/&quot;&gt;General strategy for migrating relational data to document-based&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/notes_on_migrating_storage_library/&quot;&gt;A few notes on migrating storage library&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How does Kafka know what was the last message it processed? Deep dive into Offset Tracking]]></title><description><![CDATA[Let’s say it’s Friday. Not party Friday, but Black Friday. You’re working on a busy e-commerce system that handles thousands of orders per…]]></description><link>https://event-driven.io/en/how-does-kafka-know-what-was-the/</link><guid isPermaLink="false">https://event-driven.io/en/how-does-kafka-know-what-was-the/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Let’s say it’s Friday. Not party Friday, but Black Friday. You’re working on a busy e-commerce system that handles thousands of orders per minute.&lt;/strong&gt; Suddenly, the service responsible for billing processing crashes. Until it recovers, new orders are piling up. How do you resume processing after the service restart?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Typically, you use the messaging system to accept incoming requests and then process them gradually.&lt;/strong&gt; Messaging systems have durable storage capabilities to keep messages until they’re delivered. Kafka can even keep them longer with a defined retention policy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If we’re using Kafka, when the service restarts, it can resubscribe to the topic. But which messages should we process?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One naive approach to ensure consistency might be reprocessing messages from the topic’s earliest position. That might prevent missed events, but it could also lead to a massive backlog of replayed data—and a serious risk of double processing. Would you really want to read every message on the topic from the very beginning, risking duplicate charges and triggering actions that have already been handled? Wouldn’t it be better to pick up exactly where you left off, with minimal overhead and no guesswork about what you’ve already handled?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not surprisingly, that’s what Kafka does: it has built-in offset tracking. What’s offset?&lt;/strong&gt; Offsets let each consumer record its precise position in the messages stream (logical position in the topic portion). That ensures that restarts or redeployments don’t force you to re-ingest everything you’ve ever consumed.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 728px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4d7a54c8911bf15030d10add351692f9/5e74c/2025-02-24-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAZtmmMaQ/8QAGRABAQADAQAAAAAAAAAAAAAAAQIAAxEE/9oACAEBAAEFAie5xMoe7VI89Lu20zf/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAaEAACAwEBAAAAAAAAAAAAAAAAAREhMQJB/9oACAEBAAY/AoMPSnopY1zSP//EABsQAQADAAMBAAAAAAAAAAAAAAEAESFBUWFx/9oACAEBAAE/IUFK7ubF+E2WNY8guwxcKESuYvHgE//aAAwDAQACAAMAAAAQBD//xAAWEQEBAQAAAAAAAAAAAAAAAAARAAH/2gAIAQMBAT8QNZv/xAAWEQEBAQAAAAAAAAAAAAAAAAAAESH/2gAIAQIBAT8QxH//xAAbEAEAAgMBAQAAAAAAAAAAAAABABEhMUFRgf/aAAgBAQABPxAnUNEKV3Wexr3Y2jT3GoDJehh5D4mwDh58iCcrFYwTgNgKzP/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/4d7a54c8911bf15030d10add351692f9/507e6/2025-02-24-cover.webp 200w,
/static/4d7a54c8911bf15030d10add351692f9/28a80/2025-02-24-cover.webp 400w,
/static/4d7a54c8911bf15030d10add351692f9/68c2d/2025-02-24-cover.webp 728w&quot;
              sizes=&quot;(max-width: 728px) 100vw, 728px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/4d7a54c8911bf15030d10add351692f9/37402/2025-02-24-cover.jpg 200w,
/static/4d7a54c8911bf15030d10add351692f9/4cda9/2025-02-24-cover.jpg 400w,
/static/4d7a54c8911bf15030d10add351692f9/5e74c/2025-02-24-cover.jpg 728w&quot;
            sizes=&quot;(max-width: 728px) 100vw, 728px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/4d7a54c8911bf15030d10add351692f9/5e74c/2025-02-24-cover.jpg&quot;
            alt=&quot;https://images.stockcake.com/public/9/b/f/9bf7b153-20d4-456a-97da-bb96e9f71147_large/office-paper-overload-stockcake.jpg&quot;
            title=&quot;https://images.stockcake.com/public/9/b/f/9bf7b153-20d4-456a-97da-bb96e9f71147_large/office-paper-overload-stockcake.jpg&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Services that consume message streams can restart or be redeployed for countless reasons: you might update a container in Kubernetes, roll out a patch on a bare-metal server, or autoscale in a cloud environment.&lt;/p&gt;
&lt;p&gt;In this article, we’ll look at how Kafka manages offsets under the hood, the failure scenarios you must prepare for, and how offsets help you keep your system consistent—even when services are constantly starting and stopping. We’ll also see how other technologies tackle a similar challenge.&lt;/p&gt;
&lt;p&gt;As always, we take a specific tool and try to extend it to the wide architecture level.&lt;/p&gt;
&lt;p&gt;Check also previous articles in these series:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how-a-kafka-like-producer-writes/&quot;&gt;How Kafka Producer Works internally&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/kafka-consumers-under-the-hood-of/&quot;&gt;Kafka Consumers: Under the Hood of Message Processing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/understanding-kafkas-consumer-protocol/&quot;&gt;Understanding Kafka’s Consumer Protocol: A Deep Dive into How Consumers Talk to Brokers&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;Let’s do the thought experiment. Stop now for a moment and consider how you would implement offset storage for a messaging system. Remember your findings, or better yet, note them down.&lt;/p&gt;
&lt;p&gt;Are you ready? Let’s now discuss various options we could use, and check how Kafka offset storage evolved and why.&lt;/p&gt;
&lt;h2 id=&quot;1-early-attempts-at-storing-offsets&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-early-attempts-at-storing-offsets&quot; aria-label=&quot;1 early attempts at storing offsets permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. Early Attempts at Storing Offsets&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Database Storage.&lt;/strong&gt; A straightforward way to track what a consumer has processed is to store the last processed message ID in a relational database or key-value store. This can be fine if you have a single consumer, but you run into trouble as soon as you introduce multiple consumers. Each consumer must update the same record, raising the risk of race conditions, &lt;a href=&quot;/en/distributed-locking-a-practical-guide/&quot;&gt;distributed locking&lt;/a&gt; or expensive transactions. Plus, if one consumer crashes mid-update, another might not know which offset is the latest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Local Files.&lt;/strong&gt; Another idea is to write the current offset to a local file on the machine running the consumer. This might spare you the overhead of a database, but it quickly becomes unmanageable if the machine dies or you need to scale out. Each new consumer instance has its own file, and there’s no easy way to keep them all consistent.&lt;/p&gt;
&lt;p&gt;As soon as you have more than one consumer—or if you want failover without losing track of progress these approaches break down. &lt;strong&gt;That’s why Kafka uses a different model.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;2-from-zookeeper-to-special-topic&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-from-zookeeper-to-special-topic&quot; aria-label=&quot;2 from zookeeper to special topic permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. From ZooKeeper to &lt;em&gt;special&lt;/em&gt; topic&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Historically, Kafka itself used &lt;a href=&quot;https://zookeeper.apache.org/&quot;&gt;Apache ZooKeeper&lt;/a&gt; to store consumer offsets.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ZooKeeper is a distributed key-value store with additional coordination features.&lt;/strong&gt; It allows distributed systems to store and retrieve data across multiple servers. It acts like a centralized database that helps different parts of a system share and manage configuration information and state. Unlike a simple key-value store, it provides features like atomic writes, watches (notifications), and hierarchical naming, making it particularly useful for configuration management and synchronization in distributed computing environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using Zookeeper to store offsets might have worked fine when you had only a few consumer groups or infrequent commits, but it wasn’t designed to handle the constant stream of updates in larger deployments.&lt;/strong&gt; Every offset commit triggered writes to ZooKeeper, and as the number of consumer groups and partitions grew, those writes multiplied, creating performance bottlenecks and stability concerns. ZooKeeper was never intended for high-throughput offset tracking, so teams running sizable Kafka clusters began encountering scaling issues—ranging from slower commit latencies to potential coordination timeouts—when offset commits overloaded ZooKeeper.&lt;/p&gt;
&lt;p&gt;To solve these problems, Kafka introduced the &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt; topic in version 0.9. This internal topic made offset management an integral part of Kafka rather than an external concern. By storing offset commits in a Kafka topic, you benefit from the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Replication&lt;/strong&gt; across brokers: If one broker fails, another has the offset data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Natural ordering&lt;/strong&gt; of commits: Commits appear in the topic in the exact order they happen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Compaction&lt;/strong&gt;: Kafka discards older offset commits for the same consumer-group-partition key, controlling the topic size.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://kafka.apache.org/090/documentation.html#oldconsumerconfigs&quot;&gt;Version 0.9 still allowed using Zookeeper through the&lt;/a&gt; &lt;em&gt;&lt;a href=&quot;https://kafka.apache.org/090/documentation.html#oldconsumerconfigs&quot;&gt;offsets.storage=zookeeper&lt;/a&gt;&lt;/em&gt; &lt;a href=&quot;https://kafka.apache.org/090/documentation.html#oldconsumerconfigs&quot;&gt;setting but made it obsolete.&lt;/a&gt; Then, Zookeeper implementation was dropped. Let’s discuss in detail why the topic for offsets was superior design.&lt;/p&gt;
&lt;h2 id=&quot;3-how-offsets-topic-works&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-how-offsets-topic-works&quot; aria-label=&quot;3 how offsets topic works permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. How offsets topic works&lt;/h2&gt;
&lt;p&gt;Although &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt; might look like any other Kafka topic from the outside, but it’s specifically optimized to track consumer positions. Let’s unpack its structure and flow:&lt;/p&gt;
&lt;h3 id=&quot;topic-structure-and-partitioning&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#topic-structure-and-partitioning&quot; aria-label=&quot;topic structure and partitioning permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Topic Structure and Partitioning&lt;/h3&gt;
&lt;p&gt;By default, it’s partitioned into a fixed number of partitions (often 50), with each partition managing offset commits for a subset of consumer groups. This design ensures that offset storage can scale horizontally—no single partition is overloaded by too many commits.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/723b938c3d52cf1aa09a8d57be0ed66e/779a0/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAABzUlEQVR42n1S227bMAzN///Otg7DsCbYw957S9tgBboYqx1bdmzHsnU/oyg3TYptBA5EkRTFy1lMY4DWgFKAJkQxJrCuVTqtSfYQ5jidoKYAa3Emi+1W4/Y2x2azR9MkY1lOuLkpsF7vcHWVoxaa7c4Bv54l7u52eHgQuL7O0dT6+BknlDKg6wz63uFwsFRd1BXZLA69RUs+pTwHex8wDJ7jo78nv9bhvEL+2XpqTaGqSuR5jqIoMI4SUo5zWDhDoHK8j58QgqN7QrynhC4wTsU5T9U6SqxRVi3quuez7SSsi7Pz+JtwQu/CcQanYrSHHCqI3xfYZZ+w235EmV3g5fkD2T5jaJboxTfCJdryC8y4OU34PmNsKw5bYtj/QPWyhMhX2JffSV+xLvIl6ZeoixWdX6HHn/+vcBwnatvOuoaede/Sgv7ZstEWIy2gEtSeEESbHfquo0UZmqVlWGvfdGN4xrygeUmpw4AF+TD0miiiqQrDtImnHGwirkn8e+VavBsmdoA58R0rzDJFBC7w9NRiPxO7ImKv1yXu70si+Bt54+MsS77HR8Hkb5p3xO4PgUnadZ4qS9Zp8mhby/a2dXQPM7ExxzpG9Cl1Tuw/tdihcQApeqYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/723b938c3d52cf1aa09a8d57be0ed66e/507e6/image-2.webp 200w,
/static/723b938c3d52cf1aa09a8d57be0ed66e/28a80/image-2.webp 400w,
/static/723b938c3d52cf1aa09a8d57be0ed66e/8d2ea/image-2.webp 800w,
/static/723b938c3d52cf1aa09a8d57be0ed66e/1c161/image-2.webp 1015w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/723b938c3d52cf1aa09a8d57be0ed66e/36ca5/image-2.png 200w,
/static/723b938c3d52cf1aa09a8d57be0ed66e/a3397/image-2.png 400w,
/static/723b938c3d52cf1aa09a8d57be0ed66e/a331c/image-2.png 800w,
/static/723b938c3d52cf1aa09a8d57be0ed66e/779a0/image-2.png 1015w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/723b938c3d52cf1aa09a8d57be0ed66e/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In this simplified flow, a consumer sends an offset commit to the Kafka broker, which appends a commit event to the appropriate partition in &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt;. That commit event is then replicated to follower brokers, and finally, the broker acknowledges success back to the consumer. Such replication across multiple brokers helps to protect against broker failures. When one broker dies, the cluster can still keep up with information on the others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You should always consider how frequently you commit offsets to get the desired performance.&lt;/strong&gt; Committing after every message assures minimal replays but burdens the broker with frequent writes. Committing in larger batches cuts down on overhead but means a crash could reprocess a bigger chunk of messages. Monitoring metrics like consumer lag, commit latency, and rebalance frequency helps you tune these factors for your workloads.&lt;/p&gt;
&lt;h3 id=&quot;message-format&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#message-format&quot; aria-label=&quot;message format permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Message Format&lt;/h3&gt;
&lt;p&gt;Under the hood, each offset commit is stored as a small message. While the actual format is internal to Kafka, you can think of it in a simplified TypeScript-esque interface:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OffsetCommit&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  groupId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;         &lt;span class=&quot;token comment&quot;&gt;// e.g. &apos;payment-service&apos;&lt;/span&gt;
  topic&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;           &lt;span class=&quot;token comment&quot;&gt;// e.g. &apos;transactions&apos;&lt;/span&gt;
  partition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;       &lt;span class=&quot;token comment&quot;&gt;// e.g. 0&lt;/span&gt;
  offset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;          &lt;span class=&quot;token comment&quot;&gt;// last processed offset&lt;/span&gt;
  metadata&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;       &lt;span class=&quot;token comment&quot;&gt;// optional metadata&lt;/span&gt;
  commitTimestamp&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// epoch time for commit&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;strong&gt;message key&lt;/strong&gt; includes the consumer group, topic, and partition, while the &lt;strong&gt;value&lt;/strong&gt; holds the offset (and potentially some metadata like a commit timestamp or user-defined string). Kafka uses these keys for log compaction, ensuring older commits are cleaned up over time.&lt;/p&gt;
&lt;h3 id=&quot;compaction-behavior&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#compaction-behavior&quot; aria-label=&quot;compaction behavior permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Compaction Behavior&lt;/h3&gt;
&lt;p&gt;Unlike normal topics that rely on a retention period, &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt; is a &lt;strong&gt;compact&lt;/strong&gt; topic. Unlike standard Kafka topics that might retain data for a certain number of days, compaction means Kafka continually cleans up older commits for the same key, so only the most recent offset for each consumer-group-partition remains in the long term. This prevents the topic from growing endlessly while still retaining enough history for short-term debugging.&lt;/p&gt;
&lt;h3 id=&quot;consumer-interaction&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#consumer-interaction&quot; aria-label=&quot;consumer interaction permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Consumer Interaction&lt;/h3&gt;
&lt;p&gt;When a consumer group coordinator needs to figure out where a consumer left off, it looks up the most recent commit in &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt;. This is how a newly started consumer—or one that has recovered from a crash—knows exactly which offset to resume from. Meanwhile, if you need to diagnose an issue or replay events, you can inspect the commit messages to see offset commits’ progression over time. This event-log structure makes debugging offset-related problems far easier than an opaque external store. Seeing log of changes can also help to detect conflicting writes and fix the offset position.&lt;/p&gt;
&lt;p&gt;Another important nuance is how these offset commits are tied to consumer groups. Each consumer group has a unique identifier, and each group can read different partitions from the same topics without interfering with other groups. The coordinator keeps track of which group-member owns which partitions, and uses the &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt; topic to store their progress. If one member fails or a rebalance occurs, another member can safely continue from the same offsets.&lt;/p&gt;
&lt;p&gt;Under the hood, offset commits flow through Kafka’s normal produce pipeline. The consumer’s commit request is routed to the appropriate partition leader for &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt;, replicated to its followers, and acknowledged back to the coordinator and consumer once it’s durably stored. With this approach, Kafka enforces the ordering of offset commits at the partition level. If multiple commits arrive quickly, the later ones override the earlier ones for the same key.&lt;/p&gt;
&lt;p&gt;Ultimately, the &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt; topic is less about storing data that users consume directly and more about being the backbone of Kafka’s robust consumer model. It combines the scalability of partitioned logs, the safety of replication, and the efficiency of log compaction—giving you a single, durable source of truth about how far each consumer has progressed in reading the stream. In practice, this means you don’t have to cobble together external databases or local files to track offsets, and you can recover from typical failures (like pod restarts or broker outages) without losing track of which messages have been handled.&lt;/p&gt;
&lt;p&gt;By centralizing offset data in Kafka itself, you avoid external bottlenecks like databases or ZooKeeper. You also gain built-in fault tolerance because offsets are replicated, so a broker crash doesn’t wipe out your progress. Compaction keeps the topic lean, and the partitioned design scales with more consumer groups.&lt;/p&gt;
&lt;p&gt;In practice, this design means you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Recover quickly after a crash without replaying the entire topic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Scale to hundreds or thousands of consumer groups without saturating a single store.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inspect or debug offset commits by peeking into the &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt; log.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-failure-handling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-failure-handling&quot; aria-label=&quot;3 failure handling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. Failure Handling&lt;/h2&gt;
&lt;p&gt;Kafka’s offset mechanism is designed to help in real-world failure scenarios. Let’s look at how it handles the most common ones:&lt;/p&gt;
&lt;h3 id=&quot;31-service-restart&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#31-service-restart&quot; aria-label=&quot;31 service restart permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3.1 Service Restart&lt;/h3&gt;
&lt;p&gt;Imagine a consumer that crashes mid-message. When it comes back (maybe via a Kubernetes pod restart), the consumer rejoins its consumer group. The group coordinator queries &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt; to find the last committed offset, handing the consumer a starting point that avoids reprocessing the entire backlog.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Last Committed Offset&lt;/strong&gt;: The consumer knows exactly which messages are safe to skip.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;In-flight Messages&lt;/strong&gt;: If a message is being processed but isn’t committed, Kafka replays it. &lt;a href=&quot;/en/outbox_inbox_patterns_and_delivery_guarantees_explained/&quot;&gt;Under at-least-once semantics, this might lead to duplicates, but exactly-once processing can avoid that&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rebalancing Impact&lt;/strong&gt;: If the crashing consumer had partitions assigned, those partitions might be moved temporarily to other consumers, and then moved back once it rejoins.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;32-network-partition-case&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#32-network-partition-case&quot; aria-label=&quot;32 network partition case permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3.2 Network Partition Case&lt;/h3&gt;
&lt;p&gt;If a consumer becomes isolated from the broker or the group coordinator, it can’t commit offsets. After restoring the network, the coordinator may have assigned the partitions to a different consumer. Any offsets that weren’t successfully committed are effectively lost, meaning some messages may be processed again. This scenario is preferable to losing data entirely, and you can further reduce duplicates by using idempotent or transactional settings.&lt;/p&gt;
&lt;h3 id=&quot;33-leader-failover&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#33-leader-failover&quot; aria-label=&quot;33 leader failover permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3.3 Leader Failover&lt;/h3&gt;
&lt;p&gt;The &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt; topic is partitioned and replicated like any other Kafka topic. If a broker that leads one of these partitions fails:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Offset Consistency During Change&lt;/strong&gt;: Replicas already have the offset commit data, so a new leader can step in.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Replication Impact&lt;/strong&gt;: The system updates its metadata, and the newly elected leader takes over offset writes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consumer Behavior&lt;/strong&gt;: Consumers keep sending offset commits to the coordinator, which routes them to the new leader automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These mechanisms together ensure that consumers rarely (if ever) lose track of which messages they’ve processed, even in the face of common distributed-systems failures.&lt;/p&gt;
&lt;h2 id=&quot;4-troubleshooting&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#4-troubleshooting&quot; aria-label=&quot;4 troubleshooting permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;4. Troubleshooting&lt;/h2&gt;
&lt;p&gt;For day-to-day operations, offset management can make or break your troubleshooting experience. If consumers lag behind, you may need to investigate whether they’re processing messages too slowly or whether the commit frequency is too low. If you see repeated rebalances, session timeouts could be misconfigured. Duplicates might indicate missed commits or transaction timeouts.&lt;/p&gt;
&lt;p&gt;Most production Kafka clusters monitor these metrics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consumer lag&lt;/strong&gt;: If lag grows indefinitely, you need more consumers or faster processing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commit rates&lt;/strong&gt;: Low commit rates risk losing more progress on failure; too high a rate can overload the broker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rebalance frequency&lt;/strong&gt;: Frequent rebalances disrupt processing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-how-are-others-doing-offset-tracking&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#5-how-are-others-doing-offset-tracking&quot; aria-label=&quot;5 how are others doing offset tracking permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;5. How are others doing offset tracking?&lt;/h2&gt;
&lt;p&gt;Comparing offset management across different systems highlights how design choices, performance goals, and operational needs shape their approaches. Kafka’s in-topic offset tracking is just one solution; here are several other well- examples and references you can explore for more insights.&lt;/p&gt;
&lt;h3 id=&quot;mongodb-oplog&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#mongodb-oplog&quot; aria-label=&quot;mongodb oplog permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MongoDB Oplog&lt;/h3&gt;
&lt;p&gt;MongoDB’s replication relies on an &lt;strong&gt;oplog&lt;/strong&gt; (operations log) that records all write operations in a replica set (&lt;a href=&quot;https://www.mongodb.com/docs/manual/core/replica-set-oplog/&quot;&gt;MongoDB docs&lt;/a&gt;). Each secondary node applies changes from the oplog in order, ensuring eventual consistency. Although it’s not strictly an offset model, the oplog shares some similarities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Chronological Record&lt;/strong&gt;: Operations are stored in an append-only manner, allowing secondaries to catch up after downtime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Primary Use Case&lt;/strong&gt;: Database replication, rather than event streaming.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trade-offs&lt;/strong&gt;: While it works well for MongoDB replication, it’s not designed for arbitrary consumer groups or application-level offsets. If you need to coordinate multiple consumers processing the same data stream, MongoDB’s oplog alone won’t fully address that scenario.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Read also more in &lt;a href=&quot;/en/the-write-ahead-log-a-foundation/&quot;&gt;The Write-Ahead Log: The underrated Reliability Foundation for Databases and Distributed systems&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;eventstoredbs-two-models&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#eventstoredbs-two-models&quot; aria-label=&quot;eventstoredbs two models permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;EventStoreDB’s Two Models&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/en/event_streaming_is_not_event_sourcing/&quot;&gt;EventStoreDB is a database, not a streaming solution&lt;/a&gt;, still it has streaming capabilities. It gives you the option to subscribe to notifications about new events. It offers two main subscription patterns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Persistent Subscriptions&lt;/strong&gt;: The server tracks checkpoints, distributing events to multiple consumers. This is handy if you want the server to manage concurrency and ensure that no event is processed by more than one competing consumer at a time. Still, it doesn’t give you ordering guarantees in case of retries, plus puts more burden on the database.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Catch-up Subscriptions&lt;/strong&gt;: The client controls its own offset (or “position”) in the stream. This model provides more flexibility—like starting from older events or re-subscribing at a particular point in history—but also increases responsibility on the application to handle offset storage and recovery. Typically, you store offsets in another database.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Read more in &lt;a href=&quot;/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/&quot;&gt;Persistent vs catch-up, EventStoreDB subscriptions in action&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;rabbitmq&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#rabbitmq&quot; aria-label=&quot;rabbitmq permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RabbitMQ&lt;/h3&gt;
&lt;p&gt;RabbitMQ is a message broker that uses &lt;strong&gt;acknowledgements&lt;/strong&gt; to confirm message handling (RabbitMQ docs). Once a consumer processes a message, it sends an ack to the server, which can then remove the message from the queue. Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Per-Queue Ack State&lt;/strong&gt;: RabbitMQ expects an ack for each delivery instead of tracking offsets in a log.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scaling&lt;/strong&gt;: Multiple workers can consume each queue, but the broker decides how messages are distributed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trade-offs&lt;/strong&gt;: It’s simpler to configure for smaller setups. However, at very large scales or in multi-datacenter contexts, coordinating queue states and handling failover can become more complicated than Kafka-style offset logs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;apache-pulsar&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#apache-pulsar&quot; aria-label=&quot;apache pulsar permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Apache Pulsar&lt;/h3&gt;
&lt;p&gt;Pulsar decouples &lt;strong&gt;storage&lt;/strong&gt; and &lt;strong&gt;serving&lt;/strong&gt; by keeping data in &lt;strong&gt;BookKeeper&lt;/strong&gt; and storing consumer positions in a metadata system (often ZooKeeper) (&lt;a href=&quot;https://pulsar.apache.org/docs/en/concepts-messaging/#subscription&quot;&gt;Pulsar subscription docs&lt;/a&gt;). Pulsar calls these positions “cursors,” which function similarly to Kafka’s offsets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cursors&lt;/strong&gt;: Each consumer or subscription maintains its own cursor, indicating how far it has read.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Horizontal Scaling&lt;/strong&gt;: Because BookKeeper manages data storage separately, Pulsar can scale storage and compute layers independently.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Trade-offs&lt;/strong&gt;: This approach can excel at very high volumes, but operating BookKeeper and ZooKeeper adds complexity. Understanding multiple layered components is crucial for stable long-term use.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As you compare these approaches, consider the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Throughput Requirements&lt;/strong&gt;: How many commits or acks do you expect per second?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Failure Model&lt;/strong&gt;: Which failure modes—node crashes, network splits, etc.—must you handle automatically?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Operational Complexity&lt;/strong&gt;: Are you comfortable operating multiple components (e.g., BookKeeper, ZooKeeper)? Do you have the capacity for advanced configuration?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Access Patterns&lt;/strong&gt;: Do you need fine-grained event replays, transactional guarantees, or simple queue semantics?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;6-tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#6-tldr&quot; aria-label=&quot;6 tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;6. TLDR&lt;/h2&gt;
&lt;p&gt;I wanted to show how offset tracking can be critical in maintaining consistent data processing across distributed systems. By keeping offsets in the &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt; topic, Kafka lets each consumer continue precisely where it left off after crashes, rebalances, or even data centre failovers. In the e-commerce example, that means you don’t have to replay every historical orders if a service restarts, significantly reducing the risk of duplicate processing.&lt;/p&gt;
&lt;p&gt;Naive solutions—like local files or database records for offsets—may work under limited conditions but become cumbersome when you have multiple consumers or need to scale. Kafka’s built-in offset model solves many of these complexities by using replication, compaction, and an internal event log to track what has been processed.&lt;/p&gt;
&lt;p&gt;In any large-scale system, verifying how offsets are stored, committed, and recovered is key to practical failure handling, especially if your systems are distributed. Understanding these trade-offs is essential for designing, monitoring, and troubleshooting real-time event-driven applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are your thoughts? Did you like the article? Shall I continue this series or start something new?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Documenting Event-Driven Architecture with EventCatalog and David Boyne]]></title><description><![CDATA[Welcome to the new week! “How to document systems build with Event-Driven Architecture?” and “How to keep our understanding about evolving…]]></description><link>https://event-driven.io/en/documenting-event-driven-architecture/</link><guid isPermaLink="false">https://event-driven.io/en/documenting-event-driven-architecture/</guid><content:encoded>&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Webinar recording&quot; src=&quot;https://www.youtube-nocookie.com/embed/bcfY-cPqNYo?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“How to document systems build with Event-Driven Architecture?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“How to keep our understanding about evolving messages definitions?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Are one of the common questions I’m being asked. For many years my usual answer was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Keep it simple, use Markdown, and make it a part of your design and development process”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Still, that does not scale, and governance requires proper practices and tooling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why I’m happy that tools like &lt;a href=&quot;https://www.eventcatalog.dev/&quot;&gt;EventCatalog&lt;/a&gt; emerged.&lt;/strong&gt; &lt;strong&gt;Well, it didn’t precisely emerge; &lt;a href=&quot;https://www.boyney.io/&quot;&gt;David Boyne&lt;/a&gt; thoroughly built it.&lt;/strong&gt; He brought not only his passion but to EDA, most importantly, his wide experience.&lt;/p&gt;
&lt;p&gt;Over the past few years, &lt;a href=&quot;https://www.boyney.io/&quot;&gt;David&lt;/a&gt; has focused on serverless and event-driven architectures, making both more accessible to everyone through content such as &lt;a href=&quot;https://serverlessland.com/event-driven-architecture/visuals&quot;&gt;EDA Visuals&lt;/a&gt;, &lt;a href=&quot;https://www.boyney.io/talks&quot;&gt;speaking&lt;/a&gt;, and &lt;a href=&quot;https://www.boyney.io/tools&quot;&gt;open source tools&lt;/a&gt;. He has also worked in AWS and on the &lt;a href=&quot;https://www.asyncapi.com/&quot;&gt;AsynApi initiative&lt;/a&gt;. He has lead teams that have built applications scaling to millions of people worldwide.&lt;/p&gt;
&lt;p&gt;Now, his main focus is on &lt;a href=&quot;https://www.eventcatalog.dev/&quot;&gt;EventCatalog&lt;/a&gt; and helping others to document and operate their Event-Driven systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why I’m super happy that he agreed to do a hands-on webinar and show us&lt;/strong&gt; &lt;strong&gt;&lt;a href=&quot;https://www.eventcatalog.dev/&quot;&gt;EventCatalog&lt;/a&gt; in practice. And this is a free webinar for all, as I believe it’s essential to spread awareness of it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What is &lt;a href=&quot;https://www.eventcatalog.dev/&quot;&gt;EventCatalog&lt;/a&gt;?&lt;/strong&gt; It is an open-source documentation tool focused on bringing discoverability to Event-Driven Architectures. EventCatalog is not locked down to any technology, broker or implementation details.&lt;/p&gt;
&lt;p&gt;David showed us the ease of use and versatility of extensions, plugins, generators, and multiple schema formats.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I love what he said during the webinar:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;_The whole idea here is for teams to document their resources. I think this is important because schemas can only go so far, right?&lt;/p&gt;
&lt;p&gt;If I were to show you a schema of an order-placed event, we’d be looking at a JSON schema. That’s great for engineers and developers, I guess, to an extent.&lt;/p&gt;
&lt;p&gt;But the question is, what’s the semantic meaning behind all of this? And I think when I said earlier about Domain-Driven Design, you know, as we do things like event storming, we discuss the meaning of the events. We discuss, like, what is this thing?&lt;/p&gt;
&lt;p&gt;We talk about naming conventions, and there is conflict in these sessions. There is meaning behind these events.&lt;/p&gt;
&lt;p&gt;So why not document them and share that meeting with others so others can discover the event and say, okay, the order placed means this, in this context, means this particular thing.&lt;/p&gt;
&lt;p&gt;Oh, great. Fantastic. I can consume that. I know exactly what it is.&lt;/p&gt;
&lt;p&gt;Versus, I guess, just guessing_&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Especially the last sentence is 🌶️&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Watch it fully, and tell us how you like it. Feel also invited to add your comments!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We’re also considering moving another one going through the existing sample (for instance, &lt;a href=&quot;https://github.com/event-driven-io/emmett-starter-postgresql&quot;&gt;this one made with Emmett&lt;/a&gt;) and showing how to add EventCatalog to the existing project.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;other-webinars&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#other-webinars&quot; aria-label=&quot;other webinars permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Other Webinars&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing&quot;&gt;#1 - From CRUD to Event Sourcing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short&quot;&gt;#2 - Keep your streams short! Or how to model Event-Sourced systems efficiently&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;#3 - Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice&quot;&gt;#4 - From CRUD to CQRS in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100&quot;&gt;#5 - Architecture Weekly 100 Edition - Live Q&amp;#x26;A&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;#6 - Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven&quot;&gt;#7 - Design and test Event-Driven projections and read models&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates&quot;&gt;#8 - Slim down your aggregates!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;#9 - Radek Maziarka - Modularization with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;#10 - PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski&quot;&gt;#11 - Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify&quot;&gt;#12 - Jeremy D. Miller: Simplify your architecture with Wolverine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic&quot;&gt;#13 - Yves Goeleven - The Fantastic 9&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-14-mateusz-jendza-why-verified&quot;&gt;#14 - Mateusz Jendza - Why Verified Credentials is the Future of Digital Identity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-15-mario-bittencourt-leveraging&quot;&gt;#15 - Mário Bittencourt: Leveraging BPMN for Seamless Team Collaboration in Software Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-1-sagas-hector-garcia&quot;&gt;#16 - Papers We Love #1 - Sagas (Hector Garcia-Molina, Kenneth Salem)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-16-simple-patterns-for-events&quot;&gt;#17 - Simple patterns for events schema versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-17-andrea-magnorsky-introducing&quot;&gt;#18 - Andrea Magnorsky: Introducing Bytesize Architecture Sessions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-18-laila-bougria-debug-your&quot;&gt;#19 - Laïla Bougriâ: Debug your thinking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;#20 - Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;#21 - Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-22-on-performance-testing/&quot;&gt;#22 - On Performance Testing with Jarosław Pałka&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;#23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/frontent-architecture-backend-architecture/&quot;&gt;#24 - Frontent Architecture, Backend Architecture or just Architecture? With Tomasz Ducin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/applying-observability-from-strategy/&quot;&gt;#25 - Applying Observability: From Strategy to Practice with Hazel Weakly&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/react-query-a-solution-for-frontend/&quot;&gt;#26 - React Query: A solution for Frontend State Management challenges? With Tomasz Ducin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;#27 - Documenting Event-Driven Architecture with EventCatalog and David Boyne&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Defining Your Paranoia Level: Navigating Change Without the Overkill]]></title><description><![CDATA[Welcome to the new week! The biggest challenge in understanding new technology or practices isn’t the time needed to learn them. A much…]]></description><link>https://event-driven.io/en/defining-your-paranoia-level-navigating/</link><guid isPermaLink="false">https://event-driven.io/en/defining-your-paranoia-level-navigating/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The biggest challenge in understanding new technology or practices isn’t the time needed to learn them.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A much bigger challenge is unlearning the practices we’ve been using so far.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Learning new technology can be challenging for many reasons. Complex APIs, unfamiliar patterns, tricky concepts, etc. But that’s not really it. The real problem is that our brains are wired with years of solutions that worked before. We’ve built intuitions, developed gut feelings about what’s &lt;em&gt;“right”&lt;/em&gt;, and gotten comfortable with certain ways of solving problems.&lt;/p&gt;
&lt;p&gt;**When a new approach contradicts these intuitions, our first reaction is usually resistance:&lt;br&gt;
**&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“That can’t be right”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“There must be a better way”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We waste time trying to make the new technology work like our old tools instead of understanding what makes it different.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Too often, when we see&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;new,&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;we think&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;hard.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-challenge-of-unlearning&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-challenge-of-unlearning&quot; aria-label=&quot;the challenge of unlearning permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Challenge of Unlearning&lt;/h2&gt;
&lt;p&gt;Let’s take document databases as an example. Many developers coming from a relational database background immediately ask:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“How do I do joins?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The real answer is simple but uncomfortable:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“The neat part is that you don’t.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/eaa553a49d56ec7bfbaa14b6a16d49b6/10bbb/2025-02-10-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 109.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAWABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAEDBAL/xAAWAQEBAQAAAAAAAAAAAAAAAAACAAH/2gAMAwEAAhADEAAAAbWeSumUjukCrFn/xAAbEAACAgMBAAAAAAAAAAAAAAABAgADERITMv/aAAgBAQABBQJSJzjeq1JJ2SdIg1JADNWuf//EABcRAAMBAAAAAAAAAAAAAAAAAAABEEH/2gAIAQMBAT8BmDR//8QAFREBAQAAAAAAAAAAAAAAAAAAASD/2gAIAQIBAT8BY//EABoQAAIDAQEAAAAAAAAAAAAAAAABAhARElH/2gAIAQEABj8CNUntcqQ49Hppqr//xAAcEAEAAgIDAQAAAAAAAAAAAAABABEhYTFBUXH/2gAIAQEAAT8hQQFcw9xXyEtMAW0lRG92GYi8rsw+wxAXOEmsn//aAAwDAQACAAMAAAAQUNi9/8QAGBEAAwEBAAAAAAAAAAAAAAAAAAERMVH/2gAIAQMBAT8QXWUuBFP/xAAYEQADAQEAAAAAAAAAAAAAAAAAAREhQf/aAAgBAgEBPxBuIpNomf/EABwQAQEAAwEBAQEAAAAAAAAAAAERACFBcTFRof/aAAgBAQABPxAzQTjMGTAOlVs4+Zyf3I8qaok8ySZpSw8/mDVL8C4vda7snmCZkoNTw9yiBp9AHuf/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/eaa553a49d56ec7bfbaa14b6a16d49b6/507e6/2025-02-10-cover.webp 200w,
/static/eaa553a49d56ec7bfbaa14b6a16d49b6/28a80/2025-02-10-cover.webp 400w,
/static/eaa553a49d56ec7bfbaa14b6a16d49b6/10636/2025-02-10-cover.webp 500w&quot;
              sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/eaa553a49d56ec7bfbaa14b6a16d49b6/37402/2025-02-10-cover.jpg 200w,
/static/eaa553a49d56ec7bfbaa14b6a16d49b6/4cda9/2025-02-10-cover.jpg 400w,
/static/eaa553a49d56ec7bfbaa14b6a16d49b6/10bbb/2025-02-10-cover.jpg 500w&quot;
            sizes=&quot;(max-width: 500px) 100vw, 500px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/eaa553a49d56ec7bfbaa14b6a16d49b6/10bbb/2025-02-10-cover.jpg&quot;
            alt=&quot;2025 02 10 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And this realization should lead to deeper questions.&lt;/strong&gt; Instead of thinking about joins, think about access patterns. Who’s reading this data? How often? What parts do they need together?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consider a game matchmaking system.&lt;/strong&gt; In a relational world, players, teams, matches, and game stats would be in separate tables, joined together for each query. The traditional mindset tells us this is the “correct” way. We should normalize everything and avoid redundancy at all costs. But let’s challenge this thinking.&lt;/p&gt;
&lt;p&gt;In a document database, you might store the match document with embedded team compositions, player stats, and match outcomes. Yes, player data is duplicated. Yes, updating a player’s username means updating multiple match documents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But let’s think about what actually happens in real games.&lt;/strong&gt; How often do players change their usernames? Even in games that allow it, it’s a relatively rare event. When it happens, it’s acceptable to take a few seconds to update historical matches - because that mirrors the reality of how games work. Players care about finding their match history, analyzing their performance trends, and seeing their progression over time. They don’t care if their old username takes a moment to update in historical matches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This isn’t just about technical choices; it’s about matching our data model to reality.&lt;/strong&gt; When players browse their match history or when you’re analyzing game patterns, you have all the context in one place. Each match document tells a complete story: who played, what happened, and how the game unfolded. This natural alignment with real-world usage patterns often leads to simpler, more maintainable systems.&lt;/p&gt;
&lt;h2 id=&quot;the-art-of-gradual-transition&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-art-of-gradual-transition&quot; aria-label=&quot;the art of gradual transition permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Art of Gradual Transition&lt;/h2&gt;
&lt;p&gt;The process of unlearning doesn’t mean throwing away everything we know. When we try to force our old habits into new technologies and practices, we often end up hurting ourselves.&lt;/p&gt;
&lt;p&gt;Take Event Sourcing. It’s a simple pattern, almost primitive. Instead of overwriting the state, we record a new event. When we need to execute business logic, we fetch previously recorded events, interpret them, make a decision, and record another fact.&lt;/p&gt;
&lt;p&gt;An &lt;a href=&quot;/en/event_stores_are_key_value_stores/&quot;&gt;event store is really just a key-value database&lt;/a&gt; in which the key is the record identifier, and the value is a list of events.&lt;/p&gt;
&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But this simplicity opens up so many possibilities and potential integrations.&lt;/strong&gt; Suddenly, it feels like our brain might explode. The old way was comfortable and we could run on autopilot. Here, we have to think, analyze, consider, and keep learning.&lt;/p&gt;
&lt;p&gt;Let me share another journey: I used to be a JavaScript and TypeScript hater. I came from a C# and Java background. I tried to use them precisely, like C# and Java. The syntax looked similar enough that it seemed natural to apply the same patterns. The result? Pain and frustration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The breakthrough came when I realized that JavaScript, despite its superficial similarities to C# and Java, is fundamentally different - and much more suited to functional programming approaches.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This realization highlights a common pitfall. Some people (and the tools they build) attempt to blindly transplant patterns from other environments without considering the local ecosystem. While they might offer a quick start for developers coming from specific backgrounds, they often stop at that first iteration without truly embracing the platform’s natural conventions. It’s like forcing square pegs into round holes - it might work initially, but it creates friction in the long run.&lt;/p&gt;
&lt;h2 id=&quot;starting-where-you-are&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#starting-where-you-are&quot; aria-label=&quot;starting where you are permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Starting Where You Are&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The key is to begin your journey from familiar territory and then iterate based on real experiences.&lt;/strong&gt; In our matchmaking system, when starting with microservices, don’t feel pressured to break everything into tiny services immediately. Start with a single matchmaking service that handles the core logic of putting players together. Get comfortable with that service boundary, understand its communication patterns with the game servers, and let the architecture evolve naturally.e key is to start with natural boundaries in your domain. In our matchmaking system, think about what actually needs to be separate: matchmaking logic has different scaling needs than player profiles, match history queries are different from active match management. Start by separating these based on real technical or business needs, not theoretical abstractions.&lt;/p&gt;
&lt;p&gt;Don’t create artificial boundaries or ceremonial interfaces just for the sake of “clean architecture”. If match creation needs player data, it needs player data - adding interfaces or abstractions won’t change that fundamental dependency. Instead, focus on keeping the data flow clean and explicit. This makes it easier to understand and change the system when real needs arise.&lt;/p&gt;
&lt;p&gt;Look for similarities between what you already know and what you’re learning. When moving to document databases, you’ll notice that a document is like a denormalized view of your data - something you’ve probably created before with JOINs. The difference is that now you’re storing it that way. Or in event sourcing, if you’ve ever used database triggers or audit logs to track changes, you’re already thinking about events - you’re just making them the primary source of truth now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For document databases, start by thinking about how you’d create a view of your data for a specific use case.&lt;/strong&gt; Your initial structure might mirror your current relational model - separate documents for players, matches, and teams. That’s okay. As you work with this model, you’ll start seeing where this doesn’t fit: maybe you’re constantly joining match data with team data in your queries. These pain points guide you toward better document structures, like embedding team data within match documents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In event sourcing, look for places where you’re already tracking changes:&lt;/strong&gt; &lt;em&gt;PlayerCreated&lt;/em&gt;, &lt;em&gt;MatchStarted&lt;/em&gt; and &lt;em&gt;TeamFormed&lt;/em&gt;. These are probably lurking in your audit logs or status fields. Now, you’re just making them explicit. Keep your existing business logic at first, but start expressing it with events. Instead of updating a status field to ”&lt;em&gt;InProgress&lt;/em&gt;”, you’re recording a &lt;em&gt;MatchStarted&lt;/em&gt; event. The business logic is the same - you’re just approaching it from a different angle.&lt;/p&gt;
&lt;p&gt;The key is recognizing that new patterns often have parallels in what you’re already doing. Find these familiar elements and use them as bridges to understand the new concepts. This makes the transition feel more natural and helps you avoid the temptation to force old patterns where they don’t belong.&lt;/p&gt;
&lt;h2 id=&quot;the-simplicity-paradox&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-simplicity-paradox&quot; aria-label=&quot;the simplicity paradox permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Simplicity Paradox&lt;/h2&gt;
&lt;p&gt;Sometimes what seems like the most straightforward solution isn’t actually the simplest. Our instinct is to solve problems directly: store the current state? Just update a field in the database. Need history? Add a status log table. Need to track changes? Add some timestamps.&lt;/p&gt;
&lt;p&gt;This direct approach feels natural at first. But as requirements grow, we keep adding more and more patches: audit logs, status histories, temporal queries, analytics tables. Each addition seems reasonable on its own, but they pile up into a complex mess.&lt;/p&gt;
&lt;p&gt;Take our matchmaking system. In a traditional approach, you’d store the current match state. Straightforward, right? But then:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You need to know why matches take so long to form → Add a &lt;em&gt;formation_time&lt;/em&gt; field&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Product wants to know why players decline matches → Add a &lt;em&gt;decline_reason&lt;/em&gt; field&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Analytics needs player acceptance patterns → Create a &lt;em&gt;match_responses&lt;/em&gt; table&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Support needs to debug matching issues → Add logging and tracing&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ML team wants to improve matching → Create analytics tables&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each addition makes sense alone, but together, they create a tangled web of data.&lt;/p&gt;
&lt;p&gt;Now, look at event sourcing. It follows a natural pattern - just write down what happened:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;PlayerQueuedForMatch

SkillLevelAssessed

TeamCompositionProposed

PlayerDeclinedMatch

TeamCompositionProposed

PlayerAcceptedMatch

MatchStarted&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Just by recording these events in sequence, you get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Formation time analysis (check timestamps between Queue and Start)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Decline patterns (they’re right there in the events)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Player behavior (acceptances, declines, timing)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Debug data (the full sequence shows what happened)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Analytics data (everything’s in the event stream)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What seemed like extra work actually eliminated a whole category of problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s the paradox: the less obvious path sometimes leads to radical simplicity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;By simply writing down what happened, we get capabilities that would have required multiple add-ons in the traditional approach.&lt;/p&gt;
&lt;p&gt;The power of this approach becomes even clearer when you need to add new features. Want to analyze why certain matches take longer to form? The events show the complete formation process. Need to understand patterns in player acceptance rates? The event stream tells the story. Want to implement better skill matching based on historical performance? Again, the events provide the perfect foundation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This pattern repeats across technology choices. What looks like the “simple, obvious solution” often carries hidden complexity that only reveals itself over time.&lt;/strong&gt; Meanwhile, approaches that might initially make us pause - because they’re unfamiliar or seem to require more upfront thinking - can eliminate entire categories of problems.&lt;/p&gt;
&lt;p&gt;The trick is learning to spot these patterns, to recognize when the “simple” solution will paint us into a corner, and when a different approach might open up new possibilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s not about choosing complexity but recognizing when apparent simplicity is deceiving.&lt;/strong&gt; Sometimes, what feels like a step away from simplicity is a step toward it.&lt;/p&gt;
&lt;h2 id=&quot;the-trap-of-familiar-patterns&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-trap-of-familiar-patterns&quot; aria-label=&quot;the trap of familiar patterns permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Trap of Familiar Patterns&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;When developers first encounter distributed matchmaking systems, they often try to make them behave like single-server implementations.&lt;/strong&gt; They try to maintain perfect consistency: every player must always see the same lobby state, team formations must be atomic, and skill ratings must be immediately consistent. This leads to complex synchronization mechanisms and fragile timing dependencies.&lt;/p&gt;
&lt;p&gt;A fresh perspective accepts that different players might momentarily see slightly different lobby states. Instead of fighting against network latency, design the system to work with it. Show local changes immediately, reconcile differences gradually, and focus on what matters: quickly getting players into balanced, fun matches.&lt;/p&gt;
&lt;h2 id=&quot;understanding-your-real-motivations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#understanding-your-real-motivations&quot; aria-label=&quot;understanding your real motivations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Understanding Your Real Motivations&lt;/h2&gt;
&lt;p&gt;Before diving into new technology, take a step back and ask yourself some honest questions: What problems are you actually trying to solve? Not the theoretical ones or the ones you read about in blog posts, but your actual, day-to-day pain points. If you’re considering event sourcing, is it because you need better audit trails and temporal queries or because it’s the hot new thing?&lt;/p&gt;
&lt;p&gt;You can do it with the following steps:&lt;/p&gt;
&lt;h3 id=&quot;1-assessing-your-current-state&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-assessing-your-current-state&quot; aria-label=&quot;1 assessing your current state permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. Assessing Your Current State&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;What’s actually broken in your current approach?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which parts of your system cause the most maintenance headaches?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Where do you spend most of your debugging time?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What features are hard to implement with your current architecture?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-evaluating-team-readiness&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-evaluating-team-readiness&quot; aria-label=&quot;2 evaluating team readiness permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. Evaluating Team Readiness&lt;/h3&gt;
&lt;p&gt;Your team’s context matters more than you might think:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;How much bandwidth does your team have for learning and experimentation?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What’s your deadline pressure like?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Who on the team has experience with similar technologies?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How will you handle the knowledge transfer?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-reality-checks&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-reality-checks&quot; aria-label=&quot;3 reality checks permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. Reality Checks&lt;/h3&gt;
&lt;p&gt;Before making big changes, consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Can you start with a small, contained part of your system?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What’s the minimum change that would solve your immediate problems?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How will you measure whether the change is successful?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What’s your rollback strategy if things go wrong?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After you do it and understand both your motivations and limitations, you can start…&lt;/p&gt;
&lt;h2 id=&quot;setting-your-paranoia-level&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#setting-your-paranoia-level&quot; aria-label=&quot;setting your paranoia level permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Setting Your Paranoia Level&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I call this finding your “paranoia level” - we must set it to keep ourselves sane.&lt;/strong&gt; Your paranoia level will change depending on your project’s phase and context. Let’s break it down into practical stages:&lt;/p&gt;
&lt;h3 id=&quot;finding-your-footing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#finding-your-footing&quot; aria-label=&quot;finding your footing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Finding Your Footing&lt;/h3&gt;
&lt;p&gt;Start from what you know. Your initial paranoia should be low now; focus on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Identifying patterns in your current approach that are similar to the new one,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understanding what problems the new approach is actually trying to solve&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Looking for small, safe places to try the new approach alongside the familiar one&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Recognizing which parts of your existing knowledge transfer and which might lead you astray&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You’re not jumping into the deep end. You’re building bridges between what you know and what you’re learning. This helps you spot both opportunities and potential pitfalls early.&lt;/p&gt;
&lt;h3 id=&quot;building-confidence&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#building-confidence&quot; aria-label=&quot;building confidence permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Building Confidence&lt;/h3&gt;
&lt;p&gt;As your understanding grows, your paranoia shifts. Now you start seeing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Where the approach naturally fits and where it doesn’t&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which problems it solve elegantly and which it makes harder&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What trade-offs you’re making&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Where you need to be extra careful&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is when you start recognizing patterns and anti-patterns from experience, not just theory.&lt;/p&gt;
&lt;h3 id=&quot;mature-understanding&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#mature-understanding&quot; aria-label=&quot;mature understanding permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Mature Understanding&lt;/h3&gt;
&lt;p&gt;At this point, your paranoia becomes more nuanced. You develop:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A clear sense of what can go wrong and what’s unlikely to be an issue&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understanding of which parts need extra attention,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Confidence in making trade-off decisions,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Recognition of when to keep things simple vs when to add sophistication.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Your paranoia level isn’t about being more or less careful; it’s about being more precise in where you focus your attention, like a lens focusing on what matters most at each stage of your learning journey.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A pragmatic approach means knowing when to ramp up your paranoia.&lt;/p&gt;
&lt;p&gt;Coming back to our Event Sourcing example. When you’re first exploring it, and your system isn’t in production yet, don’t worry about versioning - focus on getting your event model right. But as you get closer to production and other teams start depending on your events, that’s when you need to build your versioning muscles. Start thinking about how changes to your events will impact consumers, how you’ll handle schema evolution, and what your migration strategy will look like.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The key is timing: too early, you’ll build complexity you don’t need yet, too late, and you risk breaking other teams’ code.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;learning-through-controlled-experiments&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#learning-through-controlled-experiments&quot; aria-label=&quot;learning through controlled experiments permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Learning Through Controlled Experiments&lt;/h2&gt;
&lt;p&gt;Before committing to a new technology stack for your system, create a “spike” project that tests your riskiest assumptions. Build a minimal prototype that handles concurrent player queues. Model your most complex team formation patterns. These experiments should be throwaway code, but they’ll teach you more than any amount of reading.&lt;/p&gt;
&lt;p&gt;Do precise benchmarks and load tests, ensuring the riskiest assumptions are correct.&lt;/p&gt;
&lt;p&gt;The key is to focus these experiments on your actual pain points. Don’t test generic scenarios - test your specific challenges. How does your chosen technology handle sudden spikes in player queue joins? How does it manage team formation across different skill tiers? How does it handle players dropping out mid-formation?&lt;/p&gt;
&lt;h2 id=&quot;arent-we-adding-technical-debt&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#arent-we-adding-technical-debt&quot; aria-label=&quot;arent we adding technical debt permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Aren’t we adding technical debt?&lt;/h2&gt;
&lt;p&gt;You already know that &lt;a href=&quot;/en/tech-debt-doesnt-exist-but-trade/&quot;&gt;I believe that technical debt doesn’t exist, but tradeoffs do&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sometimes, what looks like a subpar solution from one perspective is actually a pragmatic solution from another, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Duplicated player data in match documents isn’t technical debt if it serves your access patterns well.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A monolithic matchmaker isn’t technical debt just because microservices exist.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The key is being intentional about your choices and documenting your reasoning.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is where your paranoia level becomes crucial. High paranoia might lead you to treat every deviation from “best practices” as technical debt that must be fixed immediately. Lower paranoia might help you recognize that some supposed best practices don’t matter for your case.&lt;/p&gt;
&lt;p&gt;Also, most importantly, high paranoia can make you put into analysis paralysis and stop you from even trying new approaches. Read also more in &lt;a href=&quot;/en/why_are_we_afraid_of_our_decisions/&quot;&gt;Why are we afraid of our decisions?&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;moving-forward&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#moving-forward&quot; aria-label=&quot;moving forward permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Moving Forward&lt;/h2&gt;
&lt;p&gt;The beauty of this approach is that it scales with your experience. As you encounter more real-world scenarios, your paranoia level naturally adjusts. You learn where simplicity serves you well, and additional complexity is needed. The goal isn’t to eliminate all risks but to understand which ones matter for your specific context.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remember: Every architectural decision is a trade-off. The trick isn’t finding the perfect solution - it’s understanding the trade-offs well enough to make informed decisions.&lt;/strong&gt; Try implementing things in practice, build up some muscle memory, and then revisit whether your paranoia level is too high or too low.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your paranoia level isn’t just about being careful - it’s about understanding where that care is most needed.&lt;/strong&gt; Looking at new technologies means finding the balance between respecting their power and not being afraid to use them. The patterns that worked before aren’t wrong - they’re just part of your journey to understanding when different approaches make sense.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The key isn’t to become less paranoid as you learn - it’s to become more precisely paranoid.&lt;/strong&gt; To know exactly which parts need your careful attention and which parts can flow naturally from your chosen patterns. That’s how you grow not just in knowledge but in judgment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s your usual paranoia level?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[React Query: A solution for Frontend State Management challenges? With Tomasz Ducin]]></title><description><![CDATA[Welcome to the new week! Tomasz Ducin is back with us to explain the next parts of Frontend Architecture—this time with a deeper, more hands…]]></description><link>https://event-driven.io/en/react-query-a-solution-for-frontend/</link><guid isPermaLink="false">https://event-driven.io/en/react-query-a-solution-for-frontend/</guid><content:encoded>&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Webinar recording&quot; src=&quot;https://www.youtube-nocookie.com/embed/MLO08iaRvBk?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://ducin.dev/&quot;&gt;Tomasz Ducin&lt;/a&gt; is back with us to explain the next parts of Frontend Architecture—this time with a deeper, more hands-on walkthrough of Frontend State Management on the &lt;a href=&quot;https://tanstack.com/query/latest&quot;&gt;React Query&lt;/a&gt; example&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;And this is a free episode available to all, so check it out!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why did we decide to zoom in on React Query? It’s a novel approach that can simplify front-end and back-end communication.&lt;/strong&gt; We wanted to show a practical example of a dedicated technology for handling server data, especially given how often I see teams mix local UI state and remote data in ways that cause stale info, redundant fetching, and scattered “loading” flags. Tomek walked me through exactly why React Query solves those problems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And Tomek is one of the best people I know, specialising in Frontend and Architecture.&lt;/strong&gt; He’s an Independent Consultant, Architect, Developer, Speaker, and Trainer. Expertise in Web Technologies &amp;#x26; Software Architecture. Angular Devtools Contributor. &lt;a href=&quot;https://egghead.io/q/resources-by-tomasz-ducin&quot;&gt;Egghead Instructor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Just check the video to see how he used visuals to explain React Query and state management work.&lt;/p&gt;
&lt;p&gt;What else have we discussed?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tomek emphasized the crucial distinction between purely local UI data, such as toggles or form inputs, and data you fetch from a server.&lt;/strong&gt; When these two categories are combined in one place, it becomes unclear which part of your code is responsible for updates, which can result in inconsistent behaviour.&lt;/p&gt;
&lt;p&gt;Keeping them separate helps maintain a clean structure and ensures each data part is updated in the right context.&lt;/p&gt;
&lt;p&gt;Tomek then outlined the main challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;caching strategies,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;time-to-live settings,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;stale-while-revalidate patterns.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;He also discussed query keys, which let you organize and invalidate server responses based on what actually changed—no brute-force reloading.&lt;/p&gt;
&lt;p&gt;We jumped into real code, showing how React Query handles data fetching and background prefetches and automatically updates the UI when tabs come in and out of focus. Tomek demonstrated how to set mutation hooks for things like deleting a record and how easy it is to invalidate just the relevant pieces of the cache. The DevTools panel gave a clear view of each query’s status, simplifying debugging.&lt;/p&gt;
&lt;p&gt;Tomek also showed us and proved that even though technology started in the React community, it can be used in other frameworks like Angular. Actually, it’s not called a React Query anymore, but Tanstack Query.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What surprised me was the overlap with backend patterns, like pub/sub or connection pooling.&lt;/strong&gt; Tomek’s explanation showed that when React Query decides whether to serve cached data or make a new HTTP call, it’s basically doing a form of “pooling” and “subscription” management.&lt;/p&gt;
&lt;p&gt;That’s a familiar concept in many server-side architectures, which shows how these patterns aren’t just front-end tricks. Logically, it’s not far from what we discussed in &lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;Mastering Database Connection Pooling&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So again, that showed that we should not put a fence between us and discuss whether we face Frontend or Backend Architecture, it’s &lt;em&gt;just&lt;/em&gt; Architecture!&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Webinar recording&quot; src=&quot;https://www.youtube-nocookie.com/embed/EXj9TTJQwNc?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I learned a lot, and I’m sure that you’ll learn to. Check the video, comment, and tell us how you liked it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please also send us the topic that you’d like us to discuss next time. Tomek has a lot of fuel in his back!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Check also more from Tomek:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ducin.dev/&quot;&gt;Blog&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/in/tomasz-ducin-82234a4b&quot;&gt;LinkedIn Profile&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/ducin.dev&quot;&gt;Bluesky Profile&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://egghead.io/q/resources-by-tomasz-ducin&quot;&gt;Egghead courses&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://architekturanafroncie.pl/&quot;&gt;His Polish course Architecture on Frontend&lt;/a&gt; (yes, it’s polished!)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, &lt;a href=&quot;https://tanstack.com/query/latest/docs/framework/react/overview&quot;&gt;React Query documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And other webinars!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing&quot;&gt;#1 - From CRUD to Event Sourcing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short&quot;&gt;#2 - Keep your streams short! Or how to model Event-Sourced systems efficiently&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;#3 - Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice&quot;&gt;#4 - From CRUD to CQRS in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100&quot;&gt;#5 - Architecture Weekly 100 Edition - Live Q&amp;#x26;A&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;#6 - Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven&quot;&gt;#7 - Design and test Event-Driven projections and read models&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates&quot;&gt;#8 - Slim down your aggregates!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;#9 - Radek Maziarka - Modularization with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;#10 - PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski&quot;&gt;#11 - Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify&quot;&gt;#12 - Jeremy D. Miller: Simplify your architecture with Wolverine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic&quot;&gt;#13 - Yves Goeleven - The Fantastic 9&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-14-mateusz-jendza-why-verified&quot;&gt;#14 - Mateusz Jendza - Why Verified Credentials is the Future of Digital Identity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-15-mario-bittencourt-leveraging&quot;&gt;#15 - Mário Bittencourt: Leveraging BPMN for Seamless Team Collaboration in Software Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-1-sagas-hector-garcia&quot;&gt;#16 - Papers We Love #1 - Sagas (Hector Garcia-Molina, Kenneth Salem)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-16-simple-patterns-for-events&quot;&gt;#17 - Simple patterns for events schema versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-17-andrea-magnorsky-introducing&quot;&gt;#18 - Andrea Magnorsky: Introducing Bytesize Architecture Sessions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-18-laila-bougria-debug-your&quot;&gt;#19 - Laïla Bougriâ: Debug your thinking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;#20 - Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;#21 - Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-22-on-performance-testing/&quot;&gt;#22 - On Performance Testing with Jarosław Pałka&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;#23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;#24 - Frontent Architecture, Backend Architecture or just Architecture? With Tomasz Ducin&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/applying-observability-from-strategy/&quot;&gt;#25 - Applying Observability: From Strategy to Practice with Hazel Weakly&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;#26 - React Query: A solution for Frontend State Management challenges? With Tomasz Ducin&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Understanding Kafka's Consumer Protocol: A Deep Dive into How Consumers Talk to Brokers]]></title><description><![CDATA[Last week, we explored how Kafka consumers work in consumer groups and how rebalancing ensures partition distribution. That sparked an…]]></description><link>https://event-driven.io/en/understanding-kafkas-consumer-protocol/</link><guid isPermaLink="false">https://event-driven.io/en/understanding-kafkas-consumer-protocol/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Last week, we explored how Kafka consumers work in consumer groups and how rebalancing ensures partition distribution.&lt;/strong&gt; &lt;a href=&quot;https://www.architecture-weekly.com/p/kafka-consumers-under-the-hood-of/comment/87695871&quot;&gt;That sparked an interesting question&lt;/a&gt; about how consumers actually communicate with brokers and detect rebalancing needs. Thanks, Michał. The question is again on point, as that’s what I wanted to expand on!&lt;/p&gt;
&lt;p&gt;Again, communication sounds simple. Consumers just get messages from brokers, right? As I’m writing about it, you may already guess that behind the scenes, it’s not that simple.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Today, we’ll explore the communication protocol between Kafka consumers and brokers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We’ll see how what appears to be a simple “push” system is actually an elegant pull-based protocol and how rebalancing notifications occur through clever use of error codes rather than explicit push notifications.&lt;/p&gt;
&lt;h2 id=&quot;consumer-groups-why-we-need-them-and-how-they-work&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#consumer-groups-why-we-need-them-and-how-they-work&quot; aria-label=&quot;consumer groups why we need them and how they work permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Consumer Groups: Why We Need Them and How They Work&lt;/h2&gt;
&lt;p&gt;Before we jump right into the protocol details, let’s discuss a fundamental problem in message processing: how do you scale message consumption across multiple applications? Imagine you’re running an e-commerce platform processing thousands of orders per second. A single application instance couldn’t handle that load—you need to distribute the work somehow.&lt;/p&gt;
&lt;p&gt;This is where consumer groups come in. The concept is beautifully simple: instead of having a single consumer handle all messages, you can have multiple consumers work together, each handling a portion of the messages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One more powerful aspect of consumer groups is having multiple groups independently processing the same topic.&lt;/strong&gt; For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;One group might process orders for fulfillment&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Another group might analyze orders for business intelligence&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A third group might archive orders for compliance&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each group maintains its own view of which messages have been processed, allowing different applications to work at their own pace.&lt;/p&gt;
&lt;p&gt;Think of it like a team of workers in a warehouse - instead of one person trying to process all incoming packages, you have multiple workers, each handling their own set of packages.&lt;/p&gt;
&lt;h3 id=&quot;the-basic-idea-dividing-work&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-basic-idea-dividing-work&quot; aria-label=&quot;the basic idea dividing work permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Basic Idea: Dividing Work&lt;/h3&gt;
&lt;p&gt;At its core, a consumer group is just what it sounds like - a group of consumers working together. When you create a consumer in Kafka, you assign it to a group:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; kafka&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;consumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  groupId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;order-processing&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; consumer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; topic&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;orders&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But how does Kafka ensure these consumers work together efficiently? The magic lies in how Kafka divides work using partitions. Remember that each Kafka topic is split into partitions - these become our units of work distribution. Kafka ensures that each partition is assigned to exactly one consumer in the group.&lt;/p&gt;
&lt;p&gt;This simple rule has powerful implications:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Work is naturally distributed across consumers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Message ordering is preserved within each partition&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consumers can work independently without coordination&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;how-work-gets-divided-partition-assignment&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-work-gets-divided-partition-assignment&quot; aria-label=&quot;how work gets divided partition assignment permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How Work Gets Divided: Partition Assignment&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/i/155239200/partition-assignment-how-it-works&quot;&gt;We discussed that part in detail in the last edition&lt;/a&gt;, but let’s recap it and see how this works in practice.&lt;/p&gt;
&lt;p&gt;Imagine you have a topic with siz partitions and three consumers in a group. Kafka will distribute the partitions like this:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cd43dfca97b22aa10d0f7b6db2d1ee7e/064e6/2025-01-27-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 29.500000000000004%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA9ElEQVR42oVR7W6EMAzj/d9ymrSDY+MKNOlH2npuOZ10P6ZVipLixHXM1Br+PI1g0IpjzwxDDBX/nakNxoZaK0opzIaUIkQFMQYcLuC+eKx3gT8jvzF7j5wTewtnbMy+CHeX2axQLUPNMp/YfgIsV4g3OBIWo9LQ8L0GPLZEsq6614KcGlIs2FiLL5h603zzVAUEqbh9HWzMMAN2l0hykrxBiX1+ON4jKIxYxPrEeizzDvdImLpMFaOqNCJIeRp4JVUbD3RlKvnp7YWFYDiOzC3iqMfK11ClSuEryrXf/1LkqvMs9FHpX3sjTLyvq1C1cvXLw1+EFtRSEilLRgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/cd43dfca97b22aa10d0f7b6db2d1ee7e/507e6/2025-01-27-cover.webp 200w,
/static/cd43dfca97b22aa10d0f7b6db2d1ee7e/28a80/2025-01-27-cover.webp 400w,
/static/cd43dfca97b22aa10d0f7b6db2d1ee7e/8d2ea/2025-01-27-cover.webp 800w,
/static/cd43dfca97b22aa10d0f7b6db2d1ee7e/55867/2025-01-27-cover.webp 1117w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/cd43dfca97b22aa10d0f7b6db2d1ee7e/36ca5/2025-01-27-cover.png 200w,
/static/cd43dfca97b22aa10d0f7b6db2d1ee7e/a3397/2025-01-27-cover.png 400w,
/static/cd43dfca97b22aa10d0f7b6db2d1ee7e/a331c/2025-01-27-cover.png 800w,
/static/cd43dfca97b22aa10d0f7b6db2d1ee7e/064e6/2025-01-27-cover.png 1117w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/cd43dfca97b22aa10d0f7b6db2d1ee7e/a331c/2025-01-27-cover.png&quot;
            alt=&quot;2025 01 27 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This distribution isn’t random - it’s managed by a special broker called the Group Coordinator. Think of the Group Coordinator as a supervisor, ensuring work is fairly distributed. It keeps track of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Which consumers are currently active&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which partitions are assigned to each consumer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When assignments need to change&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Consumer groups also handle the changes ensuring that work is distributed when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A new consumer joins the group.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A consumer crashes or becomes unresponsive.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Network issues cause temporary disconnects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;New partitions are created.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This is where the concept of rebalancing comes in. When any change occurs in the group, Kafka triggers a rebalancing process to redistribute partitions among the remaining consumers.&lt;/strong&gt; It’s like a warehouse supervisor reassigning work when workers come or go.&lt;/p&gt;
&lt;p&gt;The rebalancing process follows a careful sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The Group Coordinator notices a change (new consumer, failed consumer, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It pauses all message processing&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It gathers information about available consumers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It redistributes partitions among them&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Processing resumes with new assignments&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;making-it-reliable-handling-failures&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#making-it-reliable-handling-failures&quot; aria-label=&quot;making it reliable handling failures permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Making It Reliable: Handling Failures&lt;/h3&gt;
&lt;p&gt;Now, we’re getting to the more complex aspects of consumer groups. In a distributed system, lots of things can go wrong:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Consumers can crash&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Network connections can fail&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Processes can become slow or unresponsive&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://www.architecture-weekly.com/i/155239200/how-kafka-tracks-active-consumers&quot;&gt;Consumers regularly send heartbeat messages to the Group Coordinator&lt;/a&gt;, saying,&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;“I’m still here and working!”&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt; If a consumer stops sending heartbeats, the coordinator knows something’s wrong and can trigger a rebalance.&lt;/p&gt;
&lt;p&gt;Each time the group membership changes, Kafka assigns a new “generation ID” to the group. This is crucial for preventing &lt;em&gt;“split-brain”&lt;/em&gt; scenarios, where different consumers have different ideas about who should be processing what.&lt;/p&gt;
&lt;p&gt;Think of the generation ID as a group membership version number. When it changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;All consumers must get new partition assignments&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Old assignments become invalid&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consumers can’t process messages until they join the new generation&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We could model Consumer Group metadata to keep track of it as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConsumerGroupMetadata&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  groupId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  generationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// ⬅️ Increments with each membership change&lt;/span&gt;
  consumers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ComsumerMetadata&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  leader&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;When the consumer joins the group, the session is created.&lt;/strong&gt; The session is, in other words, a specific connection to a group. Staying in our warehouse work example represents the employee shift. Kafka needs to track active consumers in the group to distribute load and ensure reliable processing correctly.&lt;/p&gt;
&lt;p&gt;It allows the provided settings to detect how long the consumer group should wait for an answer from the consumer and also how long the consumer should wait to end rebalancing. In other words, employees can go home if there’s no work for them as stocktaking is happening.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; kafka&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;consumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  groupId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;order-processing&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// How long can a consumer be silent before it&apos;s considered dead?&lt;/span&gt;
  sessionTimeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// How long to wait during rebalancing?&lt;/span&gt;
  rebalanceTimeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;60000&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These timeouts help Kafka distinguish between temporary hiccups and actual failures.&lt;/p&gt;
&lt;p&gt;Now that we have recapped consumer groups and why we need them, let’s examine how the protocol actually implements all this functionality.&lt;/p&gt;
&lt;h2 id=&quot;the-protocol-foundation-the-dance-of-requests-and-responses&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-protocol-foundation-the-dance-of-requests-and-responses&quot; aria-label=&quot;the protocol foundation the dance of requests and responses permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Protocol Foundation: The Dance of Requests and Responses&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;When you write seemingly simple code to consume messages from Kafka, a complicated dance is happening behind the scenes.&lt;/strong&gt; Every interaction between consumers and brokers follows a strict protocol designed to handle the complexities of distributed systems.&lt;/p&gt;
&lt;p&gt;Let’s start with something that might seem mundane but is actually fascinating: the request header. Every single message between a consumer and broker starts with this structured header:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RequestHeader&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  apiKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;        &lt;span class=&quot;token comment&quot;&gt;// Identifies request type&lt;/span&gt;

  &lt;span class=&quot;token class-name&quot;&gt;apiVersion&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;    &lt;span class=&quot;token comment&quot;&gt;// Protocol version used&lt;/span&gt;

  correlationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Request tracking&lt;/span&gt;

  clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;      &lt;span class=&quot;token comment&quot;&gt;// Consumer identifier&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This isn’t just bureaucratic overhead - each field is crucial to reliable distributed communication. The &lt;em&gt;&lt;a href=&quot;https://kafka.apache.org/0101/protocol.html#protocol_api_keys&quot;&gt;apiKey&lt;/a&gt;&lt;/em&gt;, for instance, isn’t just a number - it’s part of a carefully designed protocol that supports operations like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fetching messages &lt;em&gt;(apiKey = 1)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Joining consumer groups &lt;em&gt;(apiKey = 11)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sending heartbeats &lt;em&gt;(apiKey = 12)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Leaving groups &lt;em&gt;(apiKey = 13)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Syncing group state &lt;em&gt;(apiKey = 14)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;But what makes this design truly elegant is how it handles version compatibility.&lt;/strong&gt; The &lt;em&gt;apiVersion&lt;/em&gt; field allows Kafka to evolve its protocol while maintaining backward compatibility. When a consumer connects to a broker, they first perform a version handshake—the consumer says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Here are the protocol versions I support”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and the broker responds,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Cool, and here are the protocol versions I support.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They then use the highest version they both understand.&lt;/p&gt;
&lt;p&gt;This might seem like overengineering, but imagine you’re running a large Kafka cluster. You want to upgrade your brokers to a new version with protocol improvements, but you can’t upgrade all your consumers simultaneously. This version negotiation allows for rolling upgrades without downtime - old consumers can talk to new brokers using old protocol versions, while new consumers can take advantage of new protocol features.&lt;/p&gt;
&lt;h2 id=&quot;the-bootstrap-process-finding-your-way-in-a-distributed-world&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-bootstrap-process-finding-your-way-in-a-distributed-world&quot; aria-label=&quot;the bootstrap process finding your way in a distributed world permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Bootstrap Process: Finding Your Way in a Distributed World&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Before a consumer can start receiving messages, it needs to navigate the Kafka cluster.&lt;/strong&gt; This bootstrap process is necessary because it solves a fundamental distributed systems problem: how do you find the correct servers to talk to in a dynamic system where things can change over time?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When a consumer connects, it doesn’t immediately try to fetch messages.&lt;/strong&gt; Instead, it goes through a careful bootstrap process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, it needs to find the consumer group coordinator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then, it needs to join its consumer group&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, it needs to get its partition assignments&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s look at how finding the coordinator works.&lt;/p&gt;
&lt;p&gt;You might think, “Well, just pick a broker from the bootstrap list”, but it’s more sophisticated than that. The group coordinator isn’t randomly assigned—it’s determined by a consistent hashing algorithm based on the group ID. This ensures that all consumers in the same group talk to the same coordinator while different groups’ coordinators are distributed across the cluster.&lt;/p&gt;
&lt;p&gt;Here’s what the process looks like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KafkaConsumer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; coordinator&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Broker &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; isGroupLeader&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; groupId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coordinator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findConsumerGroupCoordinator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;groupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; joinResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;joinGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coordinator&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;groupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isGroupLeader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; joinResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isLeader&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// If we&apos;re not the leader,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// the group leader will gather member info&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// and we need to sync to get our final assignments.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isGroupLeader&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;syncGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coordinator&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;groupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startHeartbeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;coordinator&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;groupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When a consumer needs to locate the Group Coordinator for a given group ID, it sends a &lt;em&gt;FindCoordinator&lt;/em&gt; request to any broker. Internally, Kafka hashes the group ID and uses a modulus operation against the partition count of the internal &lt;em&gt;__consumer_offsets&lt;/em&gt; topic. The partition leader for that resulting partition becomes the Group Coordinator.&lt;/p&gt;
&lt;p&gt;While sometimes referred to as &lt;a href=&quot;https://en.wikipedia.org/wiki/Consistent_hashing&quot;&gt;consistent hashing&lt;/a&gt; it’s actually a less complex &lt;a href=&quot;https://www.cs.cornell.edu/courses/cs312/2008sp/lectures/lec21.html&quot;&gt;modular hashing&lt;/a&gt;—less complex. It is &lt;em&gt;“consistent”&lt;/em&gt; because the same group ID will always map to the same partition (and thus the same coordinator), but it’s not the full ring-based scheme some might expect.&lt;/p&gt;
&lt;p&gt;Here’s how you might see it in pseudo-code:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KafkaConsumer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; offsetsTopicPartitionCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Example count&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;findConsumerGroupCoordinator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;groupId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Broker &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hash &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hashFunction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;groupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; partition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hash &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;offsetsTopicPartitionCount&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;lookupLeaderForOffsetsPartition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But this simple code hides the complexity of what’s happening. When finding the coordinator, the consumer sends a &lt;em&gt;FindCoordinator&lt;/em&gt; request to any broker. The request includes the group ID, and the broker uses the consistent hash to tell the consumer which broker is the coordinator for that group. This design ensures that coordinator selection is deterministic and distributed evenly across the cluster.&lt;/p&gt;
&lt;h2 id=&quot;the-heartbeat-dance-how-consumers-stay-alive&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-heartbeat-dance-how-consumers-stay-alive&quot; aria-label=&quot;the heartbeat dance how consumers stay alive permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Heartbeat Dance: How Consumers Stay Alive&lt;/h2&gt;
&lt;p&gt;One of the most elegant aspects of Kafka’s consumer protocol is how it handles consumer health checking and rebalancing notifications. Rather than having separate mechanisms for these two concerns, Kafka combines them through the heartbeat protocol.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every consumer in a group periodically sends heartbeat requests to the group coordinator.&lt;/strong&gt; These heartbeats serve multiple purposes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;They tell the coordinator that the consumer is still alive and processing messages&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They provide a channel for the coordinator to notify consumers about group changes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They help maintain consistent group membership&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The clever part is how rebalancing notifications works. &lt;strong&gt;Instead of having a separate push notification system, the coordinator simply responds to heartbeat requests with an error code when a rebalance is needed.&lt;/strong&gt; When a consumer gets a &lt;em&gt;REBALANCE_IN_PROGRESS&lt;/em&gt; error (code 27), it knows it needs to stop processing messages and rejoin the group.&lt;/p&gt;
&lt;p&gt;This design is smart in its simplicity. By piggybacking notifications on the existing heartbeat mechanism, Kafka avoids the need for additional connections or complex push notification systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s a pull-based solution to what might seem like a push problem.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://kafka.apache.org/protocol#The_Messages_Heartbeat&quot;&gt;heartbeat protocol&lt;/a&gt; looks like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HeartbeatRequest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  apiKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  apiVersion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  correlationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  groupId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  generationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  memberId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HeartbeatResponse&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  throttleTimeMs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  errorCode&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// This is key!&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But what makes this really interesting is how it handles various failure scenarios. For example, what happens if a consumer crashes and stops sending heartbeats? The coordinator has a session timeout (configured via &lt;em&gt;&lt;a href=&quot;https://kafka.apache.org/documentation/#consumerconfigs_session.timeout.ms&quot;&gt;session.timeout.ms&lt;/a&gt;&lt;/em&gt;) - if it doesn’t receive a heartbeat within this period, it considers the consumer dead and initiates a rebalance.&lt;/p&gt;
&lt;p&gt;This timeout needs to be carefully tuned. Setting it too low might trigger unnecessary rebalances due to temporary network issues or GC pauses. Set it too high, and your system takes longer to recover from actual failures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kafka provides detailed &lt;a href=&quot;https://kafka.apache.org/documentation/#consumer_group_monitoring&quot;&gt;consumer group metrics&lt;/a&gt; to help you tune this, for instance:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;last-heartbeat-seconds-ago&lt;/em&gt; - The number of seconds since the last controller heartbeat&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;heartbeat-rate&lt;/em&gt; - The average number of heartbeats per second&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;heartbeat-response-time-max&lt;/em&gt; - The max time taken to receive a response to a heartbeat request&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-fetch-protocol-more-than-just-getting-messages&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-fetch-protocol-more-than-just-getting-messages&quot; aria-label=&quot;the fetch protocol more than just getting messages permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Fetch Protocol: More Than Just Getting Messages&lt;/h2&gt;
&lt;p&gt;At the heart of Kafka’s consumer protocol lies the fetch mechanism. While it might seem straightforward - after all, we’re just getting messages, right? - the reality is a sophisticated protocol designed to optimize throughput, minimize latency, and handle various edge cases.&lt;/p&gt;
&lt;p&gt;Let’s look at what a fetch request actually looks like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;FetchRequest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  replicaId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;        &lt;span class=&quot;token comment&quot;&gt;// -1 for consumers&lt;/span&gt;

  maxWaitMs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;        &lt;span class=&quot;token comment&quot;&gt;// Long polling timeout&lt;/span&gt;

  minBytes&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;         &lt;span class=&quot;token comment&quot;&gt;// Minimum data to return&lt;/span&gt;

  maxBytes&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;         &lt;span class=&quot;token comment&quot;&gt;// Maximum data to return&lt;/span&gt;

  isolationLevel&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;// Read committed/uncommitted&lt;/span&gt;

  topics&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TopicFetchRequest&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TopicFetchRequest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  topic&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  partitions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PartitionFetchRequest&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PartitionFetchRequest&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  partition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  fetchOffset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;      &lt;span class=&quot;token comment&quot;&gt;// Where to start reading&lt;/span&gt;

  logStartOffset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;token comment&quot;&gt;// Earliest available offset&lt;/span&gt;

  maxBytes&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;         &lt;span class=&quot;token comment&quot;&gt;// Max bytes for partition&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This structure might look complex, but each field tells an interesting story about how Kafka optimizes message delivery. Take &lt;em&gt;maxWaitMs&lt;/em&gt; and &lt;em&gt;minBytes&lt;/em&gt;, for example. Together, they implement a clever optimization called &lt;a href=&quot;/en/long_polling_and_eventual_consistency/&quot;&gt;“long polling”&lt;/a&gt;. Instead of immediately returning if no data is available, the broker will wait up to &lt;em&gt;maxWaitMs&lt;/em&gt; for at least &lt;em&gt;minBytes&lt;/em&gt; of data to accumulate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This solves a common problem in messaging systems: how do you balance latency with efficiency?&lt;/strong&gt; If consumers poll too frequently when no data is available, they waste network resources and CPU cycles. If they poll too infrequently, they add latency to message processing. Long polling provides an elegant solution - consumers can maintain low latency when data is available while avoiding wasteful polling when it’s not.&lt;/p&gt;
&lt;p&gt;Here’s how it works in practice:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;KafkaConsumer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;FetchResponse&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; request &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      maxWaitMs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;       &lt;span class=&quot;token comment&quot;&gt;// Wait up to 500ms for data&lt;/span&gt;
      minBytes&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;       &lt;span class=&quot;token comment&quot;&gt;// Wait for at least 1KB&lt;/span&gt;
      maxBytes&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5242880&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;    &lt;span class=&quot;token comment&quot;&gt;// Don&apos;t return more than 5MB&lt;/span&gt;
      topics&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;buildTopicFetchRequests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Broker holds request until data available or timeout&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendRequest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But that’s just the beginning. The fetch protocol also includes sophisticated batching and parallel fetching capabilities. Since partitions might be on different brokers, consumers can fetch from multiple brokers simultaneously:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/168b252397a8d82d093423f21778babd/95ba4/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB/UlEQVR42m1Ty5LTMBDM/38DFLUHThzgwpFP4A4hCX4kcRxbdvSwbEm2m9E42c2SuKprZGnU6nmtvAfqesR+rwgabTtjHGfEb54X23XA+eyRZRdU5wCl+RSabFH02OeK9kf2WzkHnMoR222L3a5F00RCXAkXa7t40WG9rnA8Oki5EEoF5HlHdxs69/zAKl6aJjBJVCalhlIKllistQylDJzz7Lf4z0wY/RlhERHPV7h+Y5hIcoeqqlCWJeNw2JOiI1shaoi6RQjjayqWMIhofPt/IOz7HsMw8LooCiaM5HGdpQkO+xRKCvLTGAhaX6CVfCSMr8wUSnz9HiwhnocWs/0OI74i/f2C49/P2P36BFV+gZffSJB4QngXCe9RUvreEhzlUsPIDQabIrickML3CSafY3QZiRneE7ohkHxDyR2pAI5tDL8szxR2glORUGucYLSkPHrCjEAi2PqnOYyVmt4n/Bau+4lL8YJs/RFl8gHB/CD1sTgT25j/pzm8hWmMYWitWXVvG4xeUD9W8ENN4am3J/+v8k2Qd4FIOu63pmm4fYQQvDbGLqGF+U73QnYjvK15Us40NlmmkaaaxnC6G73lYmfipAxIkgtOJ0/Nv1Q/dsvhMNBdRftR0JWwpNHb0fhEkCjO5wMhjdzmT8VWXvBKmOcW242gohGhBv4Bpu/uLX/4jJsAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/168b252397a8d82d093423f21778babd/507e6/image-2.webp 200w,
/static/168b252397a8d82d093423f21778babd/28a80/image-2.webp 400w,
/static/168b252397a8d82d093423f21778babd/8d2ea/image-2.webp 800w,
/static/168b252397a8d82d093423f21778babd/5e2ad/image-2.webp 1023w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/168b252397a8d82d093423f21778babd/36ca5/image-2.png 200w,
/static/168b252397a8d82d093423f21778babd/a3397/image-2.png 400w,
/static/168b252397a8d82d093423f21778babd/a331c/image-2.png 800w,
/static/168b252397a8d82d093423f21778babd/95ba4/image-2.png 1023w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/168b252397a8d82d093423f21778babd/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This parallel fetching is crucial for performance but introduces its complexities. What happens if one broker is slower than others? What if a broker fails during a fetch? These scenarios need careful handling.&lt;/p&gt;
&lt;h2 id=&quot;error-handling-when-things-go-wrong&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#error-handling-when-things-go-wrong&quot; aria-label=&quot;error handling when things go wrong permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Error Handling: When Things Go Wrong&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;In distributed systems, failures are not just possible - they’re inevitable.&lt;/strong&gt; The true measure of a protocol’s robustness is how it handles these failures. Kafka’s consumer protocol includes sophisticated error-handling mechanisms for various failure scenarios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let’s start with one of the most common failures: leader changes.&lt;/strong&gt; When a partition leader changes (due to broker failure or rebalancing), consumers need to discover the new leader:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleLeaderChange&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  topic&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  partition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Refresh metadata to find new leader&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; metadata &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fetchMetadata&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;topic&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newLeader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;topics&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;topic&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;partitions&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;partition&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;leader&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Update our connection pool&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;connections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newLeader&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;establishConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newLeader&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Update our partition assignment map&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateLeaderForPartition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;topic&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; partition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; newLeader&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But leader changes are just one type of failure. Network partitions, for instance, require a different handling strategy:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleNetworkPartition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Stop heartbeats during partition&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pauseHeartbeats&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Try to reconnect with exponential backoff&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; retries &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tryReconnect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;min&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; Math&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; retries&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token number&quot;&gt;30000&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Max 30 second wait&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    retries&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// After reconnect, we need to rejoin group&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rejoinGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice the exponential backoff in the reconnection logic. This isn’t just a nice-to-have - it’s crucial for preventing thundering herd scenarios where many consumers simultaneously try to reconnect to a recovering broker.&lt;/p&gt;
&lt;p&gt;Coordinator failures present yet another challenge. When a group coordinator fails, all its consumers need to find a new coordinator:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handleCoordinatorFailure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Find new coordinator&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; coordinator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findCoordinator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Establish connection&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connectToCoordinator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;coordinator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Rejoin group with new coordinator&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;joinGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// If finding coordinator fails, back off and retry&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;retryBackoffMs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;configuration-deep-dive-the-art-of-tuning&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#configuration-deep-dive-the-art-of-tuning&quot; aria-label=&quot;configuration deep dive the art of tuning permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Configuration Deep Dive: The Art of Tuning&lt;/h2&gt;
&lt;p&gt;Understanding Kafka’s consumer protocol is one thing - tuning it for optimal performance is another. Let’s explore how different configurations affect behaviour and performance.&lt;/p&gt;
&lt;h3 id=&quot;low-latency-profile&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#low-latency-profile&quot; aria-label=&quot;low latency profile permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Low Latency Profile&lt;/h3&gt;
&lt;p&gt;When you need messages processed as quickly as possible:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lowLatencyConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Quick failure detection&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;session.timeout.ms&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;heartbeat.interval.ms&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Fast fetching&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;fetch.min.bytes&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;fetch.max.wait.ms&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Smaller batches&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;max.partition.fetch.bytes&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;262144&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// 256KB&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This configuration prioritizes speed over efficiency. Small fetch sizes and short wait times mean messages are processed quickly but at the cost of more network traffic and higher broker load.&lt;/p&gt;
&lt;h3 id=&quot;high-throughput-profile&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#high-throughput-profile&quot; aria-label=&quot;high throughput profile permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;High Throughput Profile&lt;/h3&gt;
&lt;p&gt;When you need to process large volumes of data efficiently:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; highThroughputConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// More tolerant of delays&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;session.timeout.ms&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;heartbeat.interval.ms&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Larger batches&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;fetch.min.bytes&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1048576&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// 1MB&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;fetch.max.wait.ms&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// More data per fetch&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;max.partition.fetch.bytes&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5242880&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// 5MB&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These settings maximize throughput but might increase latency. Larger batch sizes mean more efficient network usage but longer waits for processing to begin.&lt;/p&gt;
&lt;h3 id=&quot;heartbeat-management&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#heartbeat-management&quot; aria-label=&quot;heartbeat management permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Heartbeat Management&lt;/h3&gt;
&lt;p&gt;One common mistake is setting the heartbeat interval too close to the session timeout:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 👎 Risky timing&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; riskyConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;session.timeout.ms&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;heartbeat.interval.ms&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9000&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// Too close!&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 👍 Safe timing&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; safeConfig &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;session.timeout.ms&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&apos;heartbeat.interval.ms&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;  &lt;span class=&quot;token comment&quot;&gt;// 1/3 rule&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The problem with the risky configuration is that even a minor network hiccup or GC pause could trigger a rebalance. The safe configuration provides more buffer for temporary issues.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#wrapping-up&quot; aria-label=&quot;wrapping up permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Kafka’s consumer protocol is a sneaky distributed system design. Through clever protocol design and configuration options, it balances competing concerns—consistency vs. availability, latency vs. throughput, simplicity vs. flexibility—to achieve balance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I hope that understanding these mechanisms helps you:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Debug issues more effectively&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure consumers appropriately&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Handle errors robustly&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Design better Kafka-based systems&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Again, drop me comments and send me your feedback; as you see, I’m listening!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Let’s also talk on Discord; ping me if you didn’t get the invitation yet.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Kafka Consumers: Under the Hood of Message Processing]]></title><description><![CDATA[Last week, we discussed how Kafka producers work and how Kafka stores data. We discussed details like fsync, crash resiliency and why it’s…]]></description><link>https://event-driven.io/en/kafka-consumers-under-the-hood-of/</link><guid isPermaLink="false">https://event-driven.io/en/kafka-consumers-under-the-hood-of/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;/en/how-a-kafka-like-producer-writes/&quot;&gt;Last week, we discussed how Kafka producers work&lt;/a&gt; and how Kafka stores data.&lt;/strong&gt; We discussed details like &lt;a href=&quot;/en/how-a-kafka-like-producer-writes/&quot;&gt;fsync, crash resiliency&lt;/a&gt; and why it’s better to flush data on disk rarely. That was a bit of &lt;a href=&quot;https://www.explainxkcd.com/wiki/index.php/356:_Nerd_Sniping&quot;&gt;nerd sniping&lt;/a&gt;. But of course, not only. The main goal was to explain how messaging systems do the work and understand their capabilities and limitations. You cannot cheat physics; you need to pick your poison and know whether you optimise for throughput or consistency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The feedback was positive, and &lt;a href=&quot;https://www.architecture-weekly.com/p/how-a-kafka-like-producer-writes/comment/86700784&quot;&gt;Michał asked me to expand on the Kafka Consumers&lt;/a&gt;.&lt;/strong&gt; So I do today!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consuming messages might seem straightforward when working with Kafka: You subscribe to a topic and receive the messages asynchronously as notifications.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let’s stop for a moment to consider what asynchronous means here. We too often believe that we get push notifications magically. &lt;strong&gt;And actually, behind each push notification, there’s a pulling somewhere—just hidden behind the scenes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That’s how it works with Kafka; as users, we see those messages falling into one side of the pipe and falling out on the other. Puff! But technically, it’s based on polling the messages from partitions (so &lt;a href=&quot;https://www.architecture-weekly.com/i/152749693/kafka-logs-as-the-system&quot;&gt;Kafka’s Write-Ahead Log&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this article, I’ll explain Kafka’s message consumption process, focusing on how it handles partition assignments, fault tolerance, and the trade-offs involved.&lt;/strong&gt; While consuming messages in Kafka might seem as simple as subscribing to a topic, the underlying mechanics—like managing consumer groups and rebalancing partitions—are not precisely like that!&lt;/p&gt;
&lt;p&gt;We’ll explore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consumer Groups&lt;/strong&gt;: How Kafka assigns partitions to consumers in a group to ensure parallel processing without overlaps.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rebalancing&lt;/strong&gt;: The process of redistributing partitions when consumers join or leave and the impact this has on message processing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Practical Challenges&lt;/strong&gt;: Real-world implications of Kafka’s design, like pauses during rebalancing, handling uneven workloads, and storage overhead.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By the end, you’ll better grasp how Kafka consumers work, what trade-offs are involved, and how those might influence your design decisions.&lt;/p&gt;
&lt;h2 id=&quot;consumers-and-topics-a-quick-recap&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#consumers-and-topics-a-quick-recap&quot; aria-label=&quot;consumers and topics a quick recap permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Consumers and Topics: A Quick Recap&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;In Kafka, a topic is a logical abstraction used to organize messages. For example, a topic named&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;orders&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;might store all order events.&lt;/strong&gt; Each topic is divided into partitions to scale Kafka’s storage and processing capacity. Partitions are the physical storage units where Kafka messages are stored as append-only logs. Each message in a partition is identified by its offset, a monotonically increasing number.&lt;/p&gt;
&lt;p&gt;Partitions allow Kafka to distribute data across brokers and provide parallelism. Each partition can be processed independently, enabling horizontal scalability.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/af56cf29e743c0d43dd43662fc1158e9/77a0f/2025-01-20-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHElEQVR42k1Ta2/TMBTN/0QI2D8a0gQD8RATfEBIfEF7AaNsbEVFZRMwtLXdVrV0G2kSx7GdOGmTw712EUQ6sn197/G5jwSZsZCqQJxqJIRM5WAbQ+cWYSTw8tVrPH66hp3WLkxZI9MWKflxjJAGis7OluUImIyJ4tQ4h0goCLrgy4ScJ9cR9ttf8HGvjc+dr47grwCOiYR2e+liCgRs8CSFAzsxEQcmtM90gXJWk9pyYfME7JuyGCKKUm9PJClkNcbOKcXSvcDKmqYBf7zWtIZCoqgqsLVe3NVNDak9IXO4xwgBv/Sp3cHZcIwsr1xtMikRxgnMpI98cxnZ+jLMxl3k2yvIxj1K2aCpa+ebl3MPEqVIVMCvdLqH+HZ8Al1UUJSaTAXCRMKcH0Hduwn9cAl69Q7UgyXIwaHzaWpP9PZ9C99/nuKkd4aL0SWCaaJwOhg6jC9DSsNCJDGupjE0BeuVG1Crt6Hv33KkotdFqnNHaOwMj548w8bWO7xZ36KmdX1TWrsH2NzeQffoh5OtVUZFlrC/h7B7a7AHL1DsP4clmOshipLqSTXkuqXKOqVlVcNQhgF3qH8+Qv9shCl1m5tSFCWqakZ1IfI5/kPjyAq2az9WYtEU32XDc5i7DjPcLJJiJuZSOMQZIgdJe+lsIZ9p9bNLkD6Oxyz4cBxiMJxg9OtqQUx/zgK85wYw+E6Zf6tHRZPhbTyTQub4A8x9NQPSHSogAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/af56cf29e743c0d43dd43662fc1158e9/507e6/2025-01-20-cover.webp 200w,
/static/af56cf29e743c0d43dd43662fc1158e9/28a80/2025-01-20-cover.webp 400w,
/static/af56cf29e743c0d43dd43662fc1158e9/8d2ea/2025-01-20-cover.webp 800w,
/static/af56cf29e743c0d43dd43662fc1158e9/68fc1/2025-01-20-cover.webp 1200w,
/static/af56cf29e743c0d43dd43662fc1158e9/b1ef5/2025-01-20-cover.webp 1340w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/af56cf29e743c0d43dd43662fc1158e9/36ca5/2025-01-20-cover.png 200w,
/static/af56cf29e743c0d43dd43662fc1158e9/a3397/2025-01-20-cover.png 400w,
/static/af56cf29e743c0d43dd43662fc1158e9/a331c/2025-01-20-cover.png 800w,
/static/af56cf29e743c0d43dd43662fc1158e9/8537d/2025-01-20-cover.png 1200w,
/static/af56cf29e743c0d43dd43662fc1158e9/77a0f/2025-01-20-cover.png 1340w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/af56cf29e743c0d43dd43662fc1158e9/a331c/2025-01-20-cover.png&quot;
            alt=&quot;2025 01 20 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-are-consumers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-are-consumers&quot; aria-label=&quot;what are consumers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;What Are Consumers?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;consumer&lt;/strong&gt; is an application instance that connects to Kafka, reads messages from specific topic partitions, and processes them. Each consumer works with one or more partitions, pulling messages in sequence starting from a specific offset.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-are-consumer-groups&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-are-consumer-groups&quot; aria-label=&quot;what are consumer groups permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;What Are Consumer Groups?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;A consumer group is a collection of consumers that work together to consume messages from a topic.&lt;/strong&gt; The key idea behind consumer groups is parallelism with partition exclusivity:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Each partition in a topic is assigned to only one consumer within a group.&lt;/strong&gt; This ensures that no two consumers in the group process the same partition simultaneously. Thanks to that, we won’t have race conditions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consumers can be assigned to multiple partitions if there are more partitions than consumers.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If there are more consumers than partitions, some consumers will remain idle.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Consumers in different groups can consume the same partitions independently. For example:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;An &lt;em&gt;order&lt;/em&gt; consumer group might read messages about the orders to process orders.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Another group named &lt;em&gt;analytics&lt;/em&gt; could independently consume the same topic for analytics purposes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9711e7b0d68a4dd51fd44059be07bea6/5f836/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 34.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABRklEQVR42nVRy3KEIBDc//+9HFKV3XV1BUFUBATfqU6rxySHqZmhmn7AbR5bLNNRzdVTh3W2cJ1EZwqkoLEvPQ7cOjdYp6sf2HVKv+q2zgr7pvC9K0ypQf70yB4WsrSI4Q2jNWdB8hbzVBFHgU3ysvqPUGNbaoxJIsUKfddCS7prKgRX0KmC7w0GXyMNkqKSeE1y8zfhFD2MKunmiDpwLjC4BtscCYhoakERfe77kuCs4VnF/SJYxnjWgV1GEo5R4Z19QpUPaPFE9vVBckEn6ozoe7q0BS/UCPZN3B0Vq1EvjLHk2yoS0XHSZ78tk4auAh73GkXu4Olu3wSB2RnzcNh1FQWOyAbv3CJ/8dw4bKuA9wbGlEzBJ1gUI9PJ4Gr0Ld1YdSlOkur84aE8HQaXcy5IWiJ4AdcLxKFigvz8uNDnGNjnscQPl2YUW6aLHewAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/9711e7b0d68a4dd51fd44059be07bea6/507e6/image-2.webp 200w,
/static/9711e7b0d68a4dd51fd44059be07bea6/28a80/image-2.webp 400w,
/static/9711e7b0d68a4dd51fd44059be07bea6/8d2ea/image-2.webp 800w,
/static/9711e7b0d68a4dd51fd44059be07bea6/60992/image-2.webp 1102w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/9711e7b0d68a4dd51fd44059be07bea6/36ca5/image-2.png 200w,
/static/9711e7b0d68a4dd51fd44059be07bea6/a3397/image-2.png 400w,
/static/9711e7b0d68a4dd51fd44059be07bea6/a331c/image-2.png 800w,
/static/9711e7b0d68a4dd51fd44059be07bea6/5f836/image-2.png 1102w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/9711e7b0d68a4dd51fd44059be07bea6/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This isolation enables multi-use messaging: different systems can consume the same data independently while maintaining offsets and processing logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That fulfils the foundational assumption behind event-driven solutions: an event can have multiple subscribers.&lt;/strong&gt; It also provides a technical solution for decoupling from producer to consumer. The producer sends the event (or other type of message) to a topic, and multiple subscribers can trigger their flows based on that.&lt;/p&gt;
&lt;h2 id=&quot;partition-assignment-how-it-works&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#partition-assignment-how-it-works&quot; aria-label=&quot;partition assignment how it works permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Partition Assignment: How It Works&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The assignment of partitions to consumers is managed dynamically.&lt;/strong&gt; When a consumer joins or leaves the group, partitions are reassigned automatically. For example, a topic with six partitions and a consumer group of three consumers will result in each consumer being assigned two partitions. If one consumer fails, its partitions are reassigned to the remaining consumers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Partition assignment within a consumer group is handled by the Group Coordinator,&lt;/strong&gt; a special broker designated for managing group membership.&lt;/p&gt;
&lt;p&gt;Here’s how it works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;A &lt;em&gt;JoinGroup&lt;/em&gt; request is sent to the Group Coordinator when a consumer joins the group.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The coordinator collects information about all active consumers in the group and the partitions of the subscribed topic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The coordinator distributes partitions among the consumers using a partition assignment strategy (e.g., &lt;a href=&quot;https://en.wikipedia.org/wiki/Round-robin_item_allocation&quot;&gt;round-robin&lt;/a&gt; or range-based).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each consumer receives a &lt;em&gt;SyncGroup&lt;/em&gt; response with its assigned partitions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If a consumer crashes or leaves the group, the Group Coordinator triggers a rebalance, redistributing the partitions among the remaining consumers.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;To see this process in action, the naive implementation could look as follows:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GroupCoordinator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// tracking asignment of consumer to partitions&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; assignments&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;assignPartitions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; partitions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    partitions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; consumers&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; consumers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;getAssignment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course, that’s just an illustration. In reality, you wouldn’t like to recreate the assignment each time. You’d like to keep it consistent and reduce the chance of breaking the flow by reassignment. And that’s also why Kafka has a more sophisticated way of handling it.&lt;/p&gt;
&lt;p&gt;While dynamic partition and (re)assignment ensures fault tolerance, it comes with trade-offs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Temporary Pauses&lt;/strong&gt;: During rebalancing, consumers pause processing until partitions are reassigned.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Offset Considerations&lt;/strong&gt;: Reassignments rely on offsets to ensure that a new consumer can resume from the correct position.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Kafka allows fine-tuning of rebalancing behaviour with parameters like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;session.timeout.ms&lt;/code&gt;: Determines how long a consumer can remain inactive before being considered dead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;max.poll.interval.ms&lt;/code&gt;: Ensures consumers are actively polling messages to avoid being marked as inactive.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code class=&quot;language-text&quot;&gt;rebalance.timeout.ms&lt;/code&gt;: During a rebalance, if a consumer doesn’t respond within the defined rebalance timeout, it’s removed, and the rebalance is restarted.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-kafka-tracks-active-consumers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-kafka-tracks-active-consumers&quot; aria-label=&quot;how kafka tracks active consumers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How Kafka Tracks Active Consumers&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Kafka needs to know which consumers are still part of the group to manage rebalances effectively.&lt;/strong&gt; This is where heartbeats come in.&lt;/p&gt;
&lt;p&gt;A heartbeat is a lightweight signal sent by a consumer to the Group Coordinator at regular intervals. Think of it as the consumer saying, &lt;em&gt;“&lt;a href=&quot;https://www.youtube.com/watch?v=ZHwVBirqD2s&quot;&gt;I’m Still Standing!&lt;/a&gt;”.&lt;/em&gt; If the Group Coordinator doesn’t receive a heartbeat from a consumer within a certain time (defined by &lt;code class=&quot;language-text&quot;&gt;session.timeout.ms&lt;/code&gt;), it assumes the consumer is dead and triggers a rebalance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every time a rebalance occurs, Kafka increments the generation number for the group. This number uniquely identifies the group’s current state&lt;/strong&gt;. Consumers include the generation number in their requests to the coordinator (like heartbeats or partition fetch requests). If a request uses an outdated generation, Kafka rejects it.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A consumer joins the group during generation 1 and starts processing messages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A rebalance happens, moving the group to generation 2. The consumer now needs to include generation 2 in its requests. If it mistakenly uses generation 1, Kafka knows the request is invalid and ignores it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This ensures that only consumers aware of the latest group state can participate, preventing stale or conflicting actions.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 700px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/002c45136d4d24b359c8c28f35d0a206/f6cdf/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 114.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAXCAYAAAALHW+jAAAACXBIWXMAAAsTAAALEwEAmpwYAAACgklEQVR42pVV6ZqbMAzM+79kmwPCZePbJtl+qkYshGbDbvpDn7k8jEYj+XDLgVIoZMfMkSj6TLccJaYUZI2+kNGJRp3ken4XKfO33nren+Qezw8AjH6ivo3UNZ6vb3QviT6mPAdfB3enpg5UV54BJrrznhIjP8+8T1OwmQHTAhhXRsEY8mYkqxV115ra+sL3mpK3vBr+Bj9KtN1zS//eHx7pRSrBS5olesrBCRDuozOkupaG5spsRslgBuM9MewDTvHxApuWjfgBmNen4wvA+B7gNsDSac0yXIX524DP9JfIwTIzI9pChod+3wDepOxpTXUb0HBUAxepIjcqfvYTYAGLWScw0H1HQ3vltZWAhiWwX717qvAOIFgE4+hyathnhlmMopnn1ShF9flE1elEVoEdjJ9lhfdy2Em5BBTldcpzEZJkYXRP49AJc6QfrHsNmH2UtJaqTlvDs1XcqGcvshQojtWDmD4YuwMYPl1fvloGRYG+TTV3ztKWYL6jIQ8A50W74EZhgVBdIwEQWArWWj24keo1IGsxtC31Tc0FONL1cpZeri8nAUWRAHovzx7dMzamB1caqYEphgHMvAyMvmlp7AexztJViH3ANMesz3OFWUdrOYPmP1svhHWoyrMc1irD7H0Dn745HJbWA0Os24mDgAdV10kn/Ty+PlvPDIP4q6nOdDn+koJU5yNpNrLhXl4k2f5o+m7AWt5kVC8BYFS3ZsDq/Fvar+cp7owSGyHwDQy/tZKcKTlmniacUm/kkJJOSYueMHaR86apHQNk1prHmQvUdxNdzpYPrw/BWBmmgBMvSuAE/FJhPsTUkCWiu/HBxdqlTGb8w6CFtPpgLWfAv4mD7n2MJe+DAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/002c45136d4d24b359c8c28f35d0a206/507e6/image-3.webp 200w,
/static/002c45136d4d24b359c8c28f35d0a206/28a80/image-3.webp 400w,
/static/002c45136d4d24b359c8c28f35d0a206/06fd5/image-3.webp 700w&quot;
              sizes=&quot;(max-width: 700px) 100vw, 700px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/002c45136d4d24b359c8c28f35d0a206/36ca5/image-3.png 200w,
/static/002c45136d4d24b359c8c28f35d0a206/a3397/image-3.png 400w,
/static/002c45136d4d24b359c8c28f35d0a206/f6cdf/image-3.png 700w&quot;
            sizes=&quot;(max-width: 700px) 100vw, 700px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/002c45136d4d24b359c8c28f35d0a206/f6cdf/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The pseudo-code showcasing heartbeats handling can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConsumerHealth&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lastHeartbeat&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; consumerGeneration&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GroupCoordinator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; generation&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; consumers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ConsumerHealth&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// All partitions in the topic&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; partitions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; heartbeatTimeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; isRebalancing&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; intervalHandle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; NodeJS&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Timeout &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partitions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;partitions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; partitions&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Start monitoring for consumers&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startHeartbeatMonitoring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;startHeartbeatMonitoring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;intervalHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;consumers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lastHeartbeat &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; consumerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; lastHeartbeat &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;heartbeatTimeout&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Consumer &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;consumerId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; timed out. Removing from group.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;leaveGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stopHeartbeatMonitoring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;intervalHandle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;clearInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;intervalHandle&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;intervalHandle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Consumer sends a heartbeat&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;sendHeartbeat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; consumerGeneration&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;consumers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;consumerId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; is not part of the group.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumerState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;consumers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerGeneration &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generation&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Heartbeat from &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;consumerId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; rejected. Consumer generation &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;consumerGeneration&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; is outdated (current: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generation&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Update last heartbeat timestamp&lt;/span&gt;
    consumerState&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastHeartbeat &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Heartbeat from &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;consumerId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; accepted.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Heartbeats also serve another purpose: they let consumers detect when a rebalance is happening.&lt;/strong&gt; During a rebalance, consumers stop sending heartbeats until the process is completed. If a consumer tries to send a heartbeat during this time, the coordinator rejects it, signalling the consumer to wait.&lt;/p&gt;
&lt;h2 id=&quot;tracking-consumers-and-assignments-revisited&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tracking-consumers-and-assignments-revisited&quot; aria-label=&quot;tracking consumers and assignments revisited permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Tracking Consumers and assignments revisited&lt;/h2&gt;
&lt;p&gt;Having all of that, the more real-world code showing how the assignment works looks as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GroupCoordinator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Partition assignments&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; assignments&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;joinGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isRebalancing&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Rebalance already in progress. JoinGroup denied.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cosumer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;consumerId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; already joined!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;consumers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      lastHeartbeat&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      generation&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;generation
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startRebalance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;leaveGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isRebalancing&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Rebalance already in progress. LeaveGroup denied.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;consumers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;consumerId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; not part of the group.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;consumers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startRebalance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Both joining and leaving consumers force Kafka to ensure that partitions are distributed and that there are no orphaned ones.&lt;/p&gt;
&lt;h2 id=&quot;consumer-groups-ensuring-unique-partition-processing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#consumer-groups-ensuring-unique-partition-processing&quot; aria-label=&quot;consumer groups ensuring unique partition processing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Consumer Groups: Ensuring Unique Partition Processing&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;At its core, Kafka’s consumer group model ensures that every message in a topic is processed once, while scaling workloads across multiple consumers. Each partition in a topic is assigned to exactly one consumer in the group, but no more than one.&lt;/p&gt;
&lt;p&gt;This sounds simple enough, but distributed systems rarely make anything simple. Consumers join and leave groups dynamically, networks fail, and reassigning work introduces delays. Kafka’s &lt;strong&gt;Group Coordinator&lt;/strong&gt; handles these challenges, dynamically managing assignments and ensuring partitions aren’t left idle.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-partition-assignment-works&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-partition-assignment-works&quot; aria-label=&quot;how partition assignment works permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;How Partition Assignment Works&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Partition assignment is managed by the &lt;strong&gt;Group Coordinator&lt;/strong&gt;, a special broker designated to handle a specific group. When a consumer joins the group:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The consumer sends a &lt;code class=&quot;language-text&quot;&gt;JoinGroup&lt;/code&gt; request.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The coordinator assigns partitions to all active consumers in the group.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each consumer is notified of its assigned partitions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let’s see this in action with a simple round-robin assignment:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GroupCoordinator&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; assignments&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;assignPartitions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; partitions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    partitions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; consumers&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; consumers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;getAssignment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This round-robin assignment ensures an even distribution of partitions among consumers. Kafka also supports &lt;strong&gt;range-based assignment&lt;/strong&gt;, which keeps consecutive partitions together for fewer consumers.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;consumers-assignment-rebalancing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#consumers-assignment-rebalancing&quot; aria-label=&quot;consumers assignment rebalancing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Consumers Assignment Rebalancing&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Rebalancing is at the core of Kafka’s consumer group design, enabling dynamic partition assignment when group membership changes. As noted earlier, rebalancing occurs whenever the group membership changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A new consumer enters the group.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A consumer crashes or is shut down.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The topic is reconfigured, adding or removing partitions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are three main phases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;starting rebalancing,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;actual rebalancing,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;completing rebalance.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Understanding why Kafka takes these steps is important to understanding the logical order and its consequences is essential. You can read the full information in &lt;a href=&quot;https://cwiki.apache.org/confluence/display/KAFKA/KIP-848%3A+The+Next+Generation+of+the+Consumer+Rebalance+Protocol&quot;&gt;KIP-848: The Next Generation of the Consumer Rebalance Protocol&lt;/a&gt;. I’ll try to provide you with a logical understanding of how rebalancing works.&lt;/p&gt;
&lt;h3 id=&quot;1-starting-rebalancing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-starting-rebalancing&quot; aria-label=&quot;1 starting rebalancing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;1. Starting Rebalancing&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In this logical step, Kafka ensures the consumer group does not make another rebalancing.&lt;/p&gt;
&lt;p&gt;Kafka guarantees that no two rebalances occur simultaneously. If multiple events (e.g., a consumer joins and another fails) trigger a rebalance simultaneously, they will conflict without proper locking.&lt;/p&gt;
&lt;p&gt;Imagine a consumer group processing a topic with two partitions: [0, 1]. If a rebalance starts because consumer2 fails while consumer3 joins, overlapping updates would create inconsistent assignments. Singleton run of starting rebalancing ensures one change is fully processed before another begins.&lt;/p&gt;
&lt;p&gt;In our simple implementation, we could set the &lt;em&gt;isRebalancing&lt;/em&gt; flag and &lt;a href=&quot;/en/locks-queues-and-business-workflows/&quot;&gt;use the TaskProcessor known from previous articles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This method is triggered by events like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;private startRebalance(): void {
  if (this.isRebalancing) {
    throw new Error(&quot;Rebalance already in progress.&quot;);
  }

  // Lock the rebalance
  this.isRebalancing = true;

  // Increment generation number
  this.generation += 1;

  this.rebalance();
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The increment of the generation number is essential for synchronization. It marks a fresh state for the group, and all consumers must operate within this new context. Outdated consumers are excluded from further processing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-rebalancing-and-redistributing-partitions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-rebalancing-and-redistributing-partitions&quot; aria-label=&quot;2 rebalancing and redistributing partitions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;2. Rebalancing and redistributing Partitions&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;This is the phase where the actual redistribution of partitions happens. This involves removing stale assignments, recalculating new ones, and ensuring all active consumers are notified.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rebalance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumerList &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;consumers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumerList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;completeRebalance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Identify unassigned partitions&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; assignedPartitions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; unassignedPartitions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;partitions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;assignedPartitions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Filter out assignments for inactive consumers&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;consumers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Redistribute unassigned partitions&lt;/span&gt;
  unassignedPartitions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; index&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; consumer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; consumerList&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;index &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; consumerList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentAssignments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assignments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;consumer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;currentAssignments&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; partition&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Notify consumers of their new assignments&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;syncGroup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;completeRebalance&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Rebalancing enforces exclusivity:&lt;/strong&gt; every partition is assigned to exactly one consumer in the group. This avoids duplication and ensures that no two consumers process the same messages. Kafka tries to distribute partitions as evenly as possible.&lt;/p&gt;
&lt;p&gt;If a consumer leaves, its partitions must be redistributed to remaining consumers to avoid unprocessed data. If a consumer joins, the additional capacity is utilized to spread the workload more evenly.&lt;/p&gt;
&lt;p&gt;If a consumer group is processing a high-throughput topic, ensuring partitions are reassigned promptly during failures minimizes downtime. For instance, if consumer1 is processing partition &lt;code class=&quot;language-text&quot;&gt;[0]&lt;/code&gt; but fails, rebalancing ensures its messages are reassigned quickly to avoid delays.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-completing-rebalance&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-completing-rebalance&quot; aria-label=&quot;3 completing rebalance permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;3. Completing Rebalance&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Once the partition assignments are calculated and communicated to consumers, &lt;code class=&quot;language-text&quot;&gt;Kafka&lt;/code&gt; wraps up the process, you can think of it as the second step of a two-phase commit or &lt;a href=&quot;/en/distributed-locking-a-practical-guide/&quot;&gt;releasing the lock&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;private completeRebalance(): void {
  this.isRebalancing = false; // Unlock the rebalance
  console.log(`Rebalance completed. Generation: ${this.generation}`);
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Kafka unlocks the group and signals that it is stable and ready to handle processing again. At this point, all active consumers know their partition assignments and processing resumes without further interruptions. New membership changes (e.g., additional joins or leaves) can trigger fresh rebalances.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-downsides-of-kafkas-rebalancing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-downsides-of-kafkas-rebalancing&quot; aria-label=&quot;the downsides of kafkas rebalancing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Downsides of Kafka’s Rebalancing&lt;/h2&gt;
&lt;p&gt;While Kafka’s rebalancing mechanism ensures dynamic partition assignments when consumers join, leave, or fail, it introduces several challenges and tradeoffs. These can impact performance, scalability, and resource utilization, especially in uneven or dynamic workloads.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-uneven-partition-loads-and-processing-skew&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-uneven-partition-loads-and-processing-skew&quot; aria-label=&quot;1 uneven partition loads and processing skew permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;1. Uneven Partition Loads and Processing Skew&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Kafka assumes that partitions are evenly loaded, but this isn’t always true in practice.&lt;/strong&gt; Some partitions can carry significantly more data than others, leading to uneven consumer workloads.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For instance, in a topic tracking user activity, a partition key based on user ID could result in one user generating far more events than others.&lt;/strong&gt; If a single consumer is assigned this “hot” partition, it can become overloaded while other consumers remain underutilized. This imbalance impacts throughput and latency, with slower consumers delaying the overall processing pipeline.&lt;/p&gt;
&lt;p&gt;Kafka doesn’t natively split or rebalance workload within a single partition. Once a partition is assigned, the consumer must handle it entirely, making it hard to address such imbalances without re-partitioning the topic—a disruptive and often impractical operation in production.&lt;/p&gt;
&lt;p&gt;In contrast, some systems like Apache Pulsar allow splitting partitions into smaller logical ranges, distributing these across consumers for finer-grained load balancing. This flexibility makes it easier to handle skewed workloads without repartitioning.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-processing-pauses-during-rebalances&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-processing-pauses-during-rebalances&quot; aria-label=&quot;2 processing pauses during rebalances permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;2. Processing Pauses During Rebalances&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Kafka pauses processing for all consumers during a rebalance. This happens because the system locks the consumer group while recalculating assignments. During this time, no consumer can fetch messages, even if the changes only affect a subset of the group.&lt;/p&gt;
&lt;p&gt;If a rebalance occurs frequently, such as in environments with auto-scaling consumers or frequent failures, these pauses can compound, leading to noticeable delays. This can create bottlenecks for systems with strict latency requirements. A rebalance triggered by a single consumer joining or leaving disrupts the entire group, even if only one partition is being reassigned.&lt;/p&gt;
&lt;p&gt;Other systems, such as Pulsar, avoid this by assigning partitions directly on the client side. Instead of locking the entire group, each consumer negotiates with the broker for available partitions. This means unaffected consumers can continue processing, minimizing the impact of changes.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-frequent-updates-to-offset-storage&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-frequent-updates-to-offset-storage&quot; aria-label=&quot;3 frequent updates to offset storage permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;3. Frequent Updates to Offset Storage&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Kafka stores consumer offsets in a special topic called &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt;. Every time a rebalance occurs, the offsets for all partitions in the group must be updated. In environments with frequent rebalances, this can cause a spike in write operations to &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt;, increasing storage and network usage.&lt;/p&gt;
&lt;p&gt;For example, consider a group with 50 consumers processing 100 partitions. If the group frequently rebalances due to dynamic scaling, Kafka repeatedly updates the offsets for all partitions, even if only a few are reassigned. This adds unnecessary load to the cluster and can degrade performance.&lt;/p&gt;
&lt;p&gt;The retention of offsets in &lt;code class=&quot;language-text&quot;&gt;__consumer_offsets&lt;/code&gt; also creates challenges for ephemeral consumer groups. If offsets aren’t cleaned up properly, they can accumulate, increasing storage usage. This overhead becomes especially problematic in multi-tenant Kafka clusters where multiple consumer groups coexist.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-handling-workload-changes-inefficiently&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#4-handling-workload-changes-inefficiently&quot; aria-label=&quot;4 handling workload changes inefficiently permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;4. Handling Workload Changes Inefficiently&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Kafka’s approach to partition assignment is rigid. If a partition generates more traffic than others, it remains tied to a single consumer. Adding more consumers doesn’t help because Kafka doesn’t allow splitting partitions or distributing their workload across multiple consumers. Instead, the only option is to increase the number of partitions at the topic level—a decision that impacts all consumers and producers.&lt;/p&gt;
&lt;p&gt;This rigidity can create inefficiencies in real-world scenarios. For example, in systems with bursty workloads, Kafka can’t dynamically adjust partition assignments based on traffic patterns. A consumer stuck with a high-volume partition will continue to struggle, while others remain idle.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;5-rebalance-churn-in-dynamic-environments&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#5-rebalance-churn-in-dynamic-environments&quot; aria-label=&quot;5 rebalance churn in dynamic environments permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;5. Rebalance Churn in Dynamic Environments&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In environments where consumer group membership changes frequently—such as Kubernetes clusters with auto-scaling—Kafka’s rebalance mechanism can struggle. Each join or leave event triggers a full rebalance, pausing all consumers. If these events happen in rapid succession, the group can spend more time rebalancing than processing messages.&lt;/p&gt;
&lt;p&gt;For instance, imagine an auto-scaling setup where consumer instances are added and removed based on traffic. Every time an instance joins or leaves, the group is locked, partitions are reassigned, and processing halts temporarily. This inefficiency makes Kafka less suited for highly dynamic environments than systems like Pulsar, where partition reassignment is localized and doesn’t require global pauses.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;6-storage-implications-of-rebalancing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#6-storage-implications-of-rebalancing&quot; aria-label=&quot;6 storage implications of rebalancing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;6. Storage Implications of Rebalancing&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Frequent rebalances also impact Kafka’s storage layer. Consumers must fetch their assigned data from the brokers as partitions are reassigned. If a consumer previously cached data for a partition it no longer owns, that cache becomes invalid. The new consumer must fetch data from scratch, increasing disk and network IO.&lt;/p&gt;
&lt;p&gt;This creates additional load on the brokers, particularly in clusters with large partitions or high churn. The issue is compounded in scenarios where rebalances occur frequently, as consumers constantly discard and re-fetch data.&lt;/p&gt;
&lt;h2 id=&quot;tldr-honest-summary-and-trade-off-analysis&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr-honest-summary-and-trade-off-analysis&quot; aria-label=&quot;tldr honest summary and trade off analysis permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR: Honest Summary and Trade-Off Analysis&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Kafka’s consumer architecture is designed to scale horizontally, ensuring messages are processed reliably and efficiently across distributed systems.&lt;/strong&gt; By dividing topics into partitions and assigning them to consumers within a group, Kafka balances throughput and fault tolerance while maintaining exclusivity for each partition. This design enables predictable performance in high-throughput environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However, this approach isn’t without its limitations.&lt;/strong&gt; The reliance on rebalancing ensures fault tolerance when consumers join or leave, but it introduces pauses that can disrupt real-time processing pipelines. While necessary for consistency, these pauses can create bottlenecks in dynamic environments with frequent membership changes, such as those involving auto-scaling or ephemeral consumers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Offset management, though durable and reliable thanks to Kafka’s internal&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;__consumer_offsets&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;topic, can become a source of overhead in systems with high churn.&lt;/strong&gt; Frequent updates to offset storage increase storage and network demands, especially in multi-tenant environments where many consumer groups coexist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kafka’s static partitioning model, while simple and effective for balanced workloads, struggles with uneven data distribution.&lt;/strong&gt; A consumer assigned to a “hot” partition can become a bottleneck, with no easy way to dynamically rebalance the load within a single partition. This rigidity is a trade-off in favor of predictable scaling but requires careful planning and partitioning during topic design.&lt;/p&gt;
&lt;p&gt;Compared to other messaging systems like Apache Pulsar, Kafka’s approach prioritizes simplicity and high throughput but sacrifices flexibility. Pulsar’s more dynamic partitioning and assignment mechanisms address many of Kafka’s limitations but at the cost of additional complexity.&lt;/p&gt;
&lt;p&gt;Understanding these trade-offs allows you to make informed decisions about how to use Kafka effectively. It shines in scenarios where high throughput, durability, and predictable scalability are crucial. However, its limitations can become significant hurdles in environments with burst workloads, skewed data distribution, or strict low-latency requirements.&lt;/p&gt;
&lt;p&gt;By tuning Kafka’s configuration and aligning your use case with its strengths, you can achieve reliable and efficient processing while mitigating its weaknesses.&lt;/p&gt;
&lt;p&gt;Next week, we’ll discuss how offsets are stored and how data is transferred.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As always, please tell me how you liked it and what I could improve in the follow-up releases!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How Kafka Producer Works internally]]></title><description><![CDATA[Imagine you’re sending a message to Kafka by calling something simple like: We often treat this like a “black box”. We put messages on one…]]></description><link>https://event-driven.io/en/how-a-kafka-like-producer-writes/</link><guid isPermaLink="false">https://event-driven.io/en/how-a-kafka-like-producer-writes/</guid><content:encoded>&lt;p&gt;Imagine you’re sending a message to Kafka by calling something simple like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;producer.send(new ProducerRecord&amp;lt;&gt;(&quot;someTopic&quot;, &quot;Hello, Kafka!&quot;));&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;We often treat this like a “black box”. We put messages on one side and get them on the other.&lt;/strong&gt; The message leaves the producer, goes through the broker, and eventually appears in a consumer. This sounds straightforward, but behind the scenes, the technical implementation is a bit more complex.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kafka uses an append-only log for each partition, storing messages in files on disk.&lt;/strong&gt; We discussed that in detail in &lt;a href=&quot;/en/the-write-ahead-log-a-foundation/&quot;&gt;The Write-Ahead Log: The underrated Reliability Foundation for Databases and Distributed systems&lt;/a&gt;. Thanks to that, if the process crashes mid-write, Kafka detects partial data (via checksums) and discards it upon restart.&lt;/p&gt;
&lt;p&gt;As I got positive feedback on mixing the pseudocode (no-offence TypeScript!) with the concept explanation, &lt;strong&gt;let’s try to show that flow today!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Of course, we won’t replicate all real Kafka complexities (replication, huge batch format, time-based files rolling, etc.), but we try to be close enough logically to explain it and get closer to the backbone.&lt;/p&gt;
&lt;p&gt;By the end, we’ll have:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;A minimal &lt;strong&gt;producer&lt;/strong&gt; that batches messages in memory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;segmented&lt;/strong&gt; &lt;strong&gt;log&lt;/strong&gt; class that writes these batches in an &lt;strong&gt;append-only&lt;/strong&gt; manner.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A small &lt;strong&gt;fsync&lt;/strong&gt; toggle to ensure data is physically on disk (or rely on OS caching).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;recoverOnStartup&lt;/strong&gt; method that checks for partial writes if a crash occurred and truncates them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We’ll also discuss &lt;strong&gt;why&lt;/strong&gt; each piece exists and how that gives you a closer look at tooling internals.&lt;/p&gt;
&lt;p&gt;If you’re not into Kafka, that’s fine. This article can help you understand how other messaging tools are using disk, WAL, to keep their guarantees!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Before we jump into the topic, a short sidetrack. Or, actually, two.&lt;/p&gt;
&lt;p&gt;First, &lt;strong&gt;I invite you to join my online workshop, Practical Introduction to Event Sourcing.&lt;/strong&gt; I think you got a dedicated email about it, so let me just link here to the &lt;a href=&quot;https://ddd.academy/event-sourcing-practical-introduction/&quot;&gt;page with details&lt;/a&gt; and &lt;strong&gt;a special 10% discount for you. It’s available through this link: &lt;a href=&quot;https://substack.com/redirect/066f3347-b53b-4bb1-a8fc-157a3ea85982?j=eyJ1IjoiY2xkOXcifQ.JRRIcqpqbYszBYF6OnKY_6vgEnwbcsI9a4rZvTiX-0Q&quot;&gt;https://ti.to/on3/dddacademy/discount/Oskar&lt;/a&gt;.&lt;/strong&gt; Be quick, as the workshop will happen in precisely 2 weeks!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Secondly, we &lt;a href=&quot;https://github.com/event-driven-io/emmett/releases/tag/0.23.0&quot;&gt;just released&lt;/a&gt; the stable version of the MongoDB event store in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;.&lt;/strong&gt; I wrote a detailed article explaining how we did it and how you can do it. Since you’re here, you’ll surely like such &lt;a href=&quot;https://www.explainxkcd.com/wiki/index.php/356:_Nerd_Sniping&quot;&gt;nerd sniping&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;See: &lt;a href=&quot;/en/mongodb_event_store/&quot;&gt;https://event-driven.io/en/mongodb_event_store/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Making it consistent and performant was challenging, so I think that’s an interesting read. If you’re considering using key-value databases like DynamoDB and CosmosDB, this article can outline the challenges and solutions.&lt;/p&gt;
&lt;p&gt;My first choice is still on PostgreSQL, but I’m happy with the MongoDB implementation we came up with.&lt;/p&gt;
&lt;p&gt;If MongoDB is already part of your tech stack and the constraints outlined in the article are not deal-breakers, this approach can deliver a pragmatic, production-friendly solution that balances performance, simplicity, and developer familiarity.&lt;/p&gt;
&lt;p&gt;Ok, going back to our Kafka thing!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;producer-batching-the-first-step&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#producer-batching-the-first-step&quot; aria-label=&quot;producer batching the first step permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Producer Batching: The First Step&lt;/h2&gt;
&lt;p&gt;When your code calls &lt;em&gt;producer.send&lt;/em&gt;, real Kafka doesn’t instantly push that single message to the broker. Instead, it accumulates messages into &lt;strong&gt;batches&lt;/strong&gt; to reduce overhead.&lt;/p&gt;
&lt;p&gt;For example, if &lt;em&gt;&lt;a href=&quot;https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#batch-size&quot;&gt;batch.size&lt;/a&gt;&lt;/em&gt; is set to 16 KB, Kafka’s producer library tries to fill up to 16 KB of messages for a particular partition or wait until the time defined in &lt;em&gt;&lt;a href=&quot;https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#linger-ms&quot;&gt;linger.ms&lt;/a&gt;&lt;/em&gt; it’s not full, so before sending them as one record batch, this drastically improves throughput, though it can add slight latency.&lt;/p&gt;
&lt;p&gt;Below is a pseudocode that demonstrates &lt;strong&gt;why&lt;/strong&gt; we do batching at all—&lt;strong&gt;not&lt;/strong&gt; storing anything on disk or network, but collecting messages until we decide to flush:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleInMemoryProducer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; buffer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; broker&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Broker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; maxBatchSize&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;msg&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxBatchSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;msg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    broker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;buffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In real Kafka, we’d have compression, partitioner logic, etc. But the concept stands: &lt;strong&gt;accumulate&lt;/strong&gt; messages → send them in bigger chunks.&lt;/p&gt;
&lt;p&gt;Brokers are responsible for coordinating the data transfer between producer and consumers and ensuring that data is stored durable on disk.&lt;/p&gt;
&lt;p&gt;This is important for “under the hood” log writes because the broker typically writes entire batches, possibly compressed, to disk in a single append. That’s one of the essential things to know about why Kafka is performant. After the message is sent to the broker, it’s just stored in the log and transferred to consumers. No additional logic happens.&lt;/p&gt;
&lt;p&gt;As &lt;a href=&quot;/en/the-write-ahead-log-a-foundation/&quot;&gt;explained in the article about WAL&lt;/a&gt;. Kafka follows the classical WAL pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Log First:&lt;/strong&gt; Kafka producers append messages to a specific partition in a topic. Each partition is a WAL where new messages are appended at the end. Partitions are immutable—once a message is written, it cannot be modified. Each message in a partition is assigned a monotonic offset, which acts as a unique position marker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flush to Disk:&lt;/strong&gt; Kafka ensures durability by persisting messages to disk before acknowledging the write to the producer. Each Kafka broker flushes log entries to disk periodically (configurable via &lt;em&gt;&lt;a href=&quot;https://kafka.apache.org/documentation/#topicconfigs_flush.messages&quot;&gt;flush.messages&lt;/a&gt;&lt;/em&gt; or &lt;em&gt;&lt;a href=&quot;https://kafka.apache.org/documentation/#topicconfigs_flush.ms&quot;&gt;flush.ms&lt;/a&gt;&lt;/em&gt;). For stronger guarantees, Kafka producers can request acknowledgements from multiple brokers (e.g., &lt;em&gt;&lt;a href=&quot;https://kafka.apache.org/documentation/#producerconfigs_acks&quot;&gt;acks=all&lt;/a&gt;&lt;/em&gt;), ensuring data is replicated before the write is acknowledged.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Apply Changes Later:&lt;/strong&gt; Consumers read messages from the topic’s partition sequentially, starting from a specific offset. The position of a consumer within a partition is called the offset. Consumers commit their offsets to Kafka or external stores to keep track of their progress. This model allows Kafka to achieve high throughput: sequential reads and writes are fast and efficient on modern storage hardware. Consumers are responsible for state management (e.g., &lt;a href=&quot;/en/deduplication-in-distributed-systems/&quot;&gt;deduplication&lt;/a&gt; or applying the messages), which keeps Kafka lightweight.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&quot;single-file-append-the-simplest-broker-side-implementation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#single-file-append-the-simplest-broker-side-implementation&quot; aria-label=&quot;single file append the simplest broker side implementation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Single File Append: The Simplest Broker-Side Implementation&lt;/h2&gt;
&lt;p&gt;If we were to implement the broker side in a naive manner, we could keep a &lt;strong&gt;single&lt;/strong&gt; file for all messages. Whenever a batch arrives, we append it to the end of that file, storing it in the following format:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;[ offset: 4 bytes | dataLength: 4 bytes | data: N bytes ]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;offset - a logical position in the long,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;data length - how big is our record, in other words, how many bytes does the record data contain,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;data - the payload&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using Node.js &lt;em&gt;&lt;a href=&quot;https://nodejs.org/api/fs.html&quot;&gt;fs (File System)&lt;/a&gt;&lt;/em&gt; built-in library, we could code the basic append to log logic as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; fs &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;fs&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SingleFilePartitionLog&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; fd&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; nextOffset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filePath&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Open the file in append mode&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fd &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;openSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filePath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;a+&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;messages&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; offsets&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; msg &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; messages&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      offsets&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextOffset&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; header &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      header&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeInt32BE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextOffset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      header&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeInt32BE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;msg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; header&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; msg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextOffset&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; offsets&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, the broker can transfer this binary log data to consumers and&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why&lt;/strong&gt; does Kafka do an append-only approach? Because it’s:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fast&lt;/strong&gt;: sequential writes are efficient.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Crash-friendly&lt;/strong&gt;: if you crash mid-write, only the tail is incomplete.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Recovery&lt;/strong&gt;: you can discard partial writes at the end if they’re incomplete, preserving older data.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;However, a single monolithic file eventually becomes huge, making retention or partial scanning painful. Also, if we wanted to ensure data is physically on disk, we’d call &lt;a href=&quot;https://pubs.opengroup.org/onlinepubs/009695399/functions/fsync.html&quot;&gt;fsync&lt;/a&gt;, which we’ll see is a performance trade-off.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next, we’ll fix the “giant file” issue by splitting data into segments.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;segment-rolling-why-kafka-doesnt-use-one-giant-file&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#segment-rolling-why-kafka-doesnt-use-one-giant-file&quot; aria-label=&quot;segment rolling why kafka doesnt use one giant file permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Segment Rolling: Why Kafka Doesn’t Use One Giant File&lt;/h2&gt;
&lt;p&gt;It might balloon to tens or hundreds of GB if you keep appending to a single file. As we’re doing the Write-Ahead Log to make our solution resilient, deleting old data in the middle is not an option. Scanning the entire file on a crash restart is slow, and so forth.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kafka solves this with &lt;a href=&quot;https://docs.confluent.io/platform/current/installation/configuration/topic-configs.html#retention-bytes&quot;&gt;segment rolling&lt;/a&gt;.&lt;/strong&gt; Once the current segment hits a specific size (like 1GB) or an elapsed time, Kafka “rolls” to a new segment. The old segment is “sealed” and can be safely truncated (deleted) once it’s beyond the retention window. Searching also becomes more straightforward since only the active segment needs detailed scanning on the crash, and older segments can be more thoroughly indexed or eventually dropped.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hence,&lt;/strong&gt; the notion of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;A list of segments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each segment has a base offset (the offset of its first message) and a file descriptor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the active segment grows beyond a maximum number of bytes, the broker closes it and opens a new file.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We’ll reflect that in the code next.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;crash-consistency-fsync-and-partial-write-detection&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#crash-consistency-fsync-and-partial-write-detection&quot; aria-label=&quot;crash consistency fsync and partial write detection permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Crash Consistency: &lt;code class=&quot;language-text&quot;&gt;fsync&lt;/code&gt; and Partial-Write Detection&lt;/h2&gt;
&lt;p&gt;Usually, writing to a file means the OS eventually flushes data to disk, but if the machine loses power, that data might vanish. Calling &lt;em&gt;fsync(fd)&lt;/em&gt; forces the OS to flush all pages for that file descriptor to stable storage. That means once &lt;em&gt;fsync&lt;/em&gt; returns, if the process crashes, we’re confident the data is physically on disk (barring hardware-level issues).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In most UNIX-like operating systems, file descriptors are process-specific handles.&lt;/strong&gt; Even if two processes open the same physical file, each has its own file descriptor. When each process calls &lt;em&gt;fsync(fd)&lt;/em&gt;, the kernel serializes disk I/O at the block-device layer. In other words, concurrent &lt;em&gt;fsync&lt;/em&gt; calls on the same underlying file don’t corrupt data; they simply cause the OS to ensure all pending writes for that file descriptor reach stable storage. If a second &lt;em&gt;fsync&lt;/em&gt; call happens while the first is still flushing, the second may have to wait or might become a near no-op if the previous &lt;em&gt;fsync&lt;/em&gt; one has already flushed everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In Kafka’s actual design, each partition has a single broker that’s its leader. That means it owns a given log file at a time, so we rarely see two processes simultaneously calling on the same file.&lt;/strong&gt; The &lt;a href=&quot;https://www.architecture-weekly.com/i/147613456/single-writer-pattern&quot;&gt;single-writer principle&lt;/a&gt; means only one broker/partition leader actively appends data to that file, so concurrency for &lt;em&gt;fsync&lt;/em&gt; is basically not an issue. But if, hypothetically, you had two separate processes sharing the same file descriptor, the OS and kernel’s VFS layer would still handle concurrency safely—though it might degrade performance if both processes repeatedly call &lt;em&gt;fsync&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&quot;how-fsync-works-in-a-nutshell&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-fsync-works-in-a-nutshell&quot; aria-label=&quot;how fsync works in a nutshell permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How fsync Works (in a Nutshell)&lt;/h3&gt;
&lt;p&gt;When you call &lt;em&gt;fsync (fd)&lt;/em&gt;, the system call instructs the OS to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flush any dirty buffers&lt;/strong&gt; associated with the file descriptor &lt;code class=&quot;language-text&quot;&gt;fd&lt;/code&gt; to physical disk.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wait until the disk I/O is confirmed&lt;/strong&gt; (or stable enough) so that, once &lt;em&gt;fsync&lt;/em&gt; returns, data is guaranteed to survive a power loss or crash (to the best of the hardware’s ability).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On modern systems, the OS tracks which pages in memory are “dirty” for that file. &lt;em&gt;fsync&lt;/em&gt; forces them out of memory into the actual storage device. If another process calls &lt;em&gt;fsync&lt;/em&gt; concurrently for the same file, either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It will also wait, but the data might be on disk effectively once the first &lt;em&gt;fsync&lt;/em&gt; completes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The OS handles locking internally so that partial flushes do not cause corruption or race conditions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hence, multiple &lt;em&gt;fsync&lt;/em&gt; calls on the same file are safe but can reduce performance if done frequently.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-kafka-typically-does-fsync&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-kafka-typically-does-fsync&quot; aria-label=&quot;how kafka typically does fsync permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How Kafka Typically Does &lt;em&gt;fsync&lt;/em&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;As fsync is expensive if done frequently, in Kafka, the leader of a partition does not necessarily have to&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;fsync&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;data immediately for every write.&lt;/strong&gt; When a producer sends data to the leader, the leader writes it to its log (typically in memory or in the OS page cache, not yet on disk). Simultaneously, the leader propagates this data to its replicas (followers). The key point is that the ACK sent back to the producer does not require the leader to &lt;code class=&quot;language-text&quot;&gt;fsync&lt;/code&gt; the data to disk. Instead, Kafka relies on the replicas to form a redundancy group.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The leader appends the batch to its log but doesn’t immediately flush it to disk with &lt;code class=&quot;language-text&quot;&gt;fsync&lt;/code&gt;. Instead, it waits for enough followers (based on the &lt;a href=&quot;https://kafka.apache.org/documentation/#brokerconfigs_min.insync.replicas&quot;&gt;min.insync.replicas&lt;/a&gt; setting) to confirm that they’ve also received and appended the data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The leader sends an ACK to the producer only after receiving these confirmations. The assumption is that even if the leader crashes, one of the followers will still have the data.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;The idea is that replication provides redundancy, so durability is distributed across multiple brokers instead of relying on one broker’s disk persistence.&lt;/strong&gt; Even if the leader crashes without &lt;em&gt;fsyncing&lt;/em&gt;, the replicas can step in to ensure no data is lost. So, if one broker crashes, the data still exists on the followers. This approach allows Kafka to prioritize throughput by avoiding frequent fsync calls while maintaining durability through replication.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Replication doesn’t entirely eliminate the need for fsync but reduces its criticality for performance-sensitive workloads.&lt;/strong&gt; This trade-off is a cornerstone of Kafka’s design.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What Happens If Replication Fails?&lt;/strong&gt; If not enough replicas are available to confirm the write (e.g., due to network issues or broker failures), Kafka cannot rely solely on replication. In such cases, you might need the leader to fsync its data more frequently to ensure durability. This is why single-node setups (or scenarios with insufficient replicas) require more aggressive fsync configurations to protect against data loss.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://kafka.apache.org/08/documentation.html#brokerconfigs&quot;&gt;Kafka also does have configurations like:&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;log.flush.interval.messages&lt;/em&gt; - After how many messages to force a flush.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;log.flush.interval.ms&lt;/em&gt; - after how many milliseconds do to force a flush.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By default, these are typically set to “disable immediate flush” and let the OS manage flushing. Kafka also has a background thread that can flush at intervals. So, in single-broker scenarios (e.g., test/development), you might lose a small data window if the OS never physically wrote it. In multi-broker production, replication is the real safety net.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you have only one broker and want strong durability, you can set log.flush.interval.messages=1 or log.flush.interval.ms=0, but you’ll pay a big performance cost.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;implementing-segmented-log&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#implementing-segmented-log&quot; aria-label=&quot;implementing segmented log permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Implementing Segmented Log&lt;/h2&gt;
&lt;p&gt;We’ll construct a “SegmentedPartitionLog” that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Maintains multiple Segment objects.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rolls to a new segment if the active one exceeds the maximum segment bytes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Writes each batch with a “magic, CRC, recordCount, data” header.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optionally calls fsync after writing for durability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On startup, can parse the last segment’s tail to discard partial writes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;segment-and-record-structure&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#segment-and-record-structure&quot; aria-label=&quot;segment and record structure permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Segment and Record structure&lt;/h3&gt;
&lt;p&gt;Let’s define the structure of our log segment and the record. Record will look as follows&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SingleRecord&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  relativeOffset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  timestamp&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  payload&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;relativeOffset&lt;/em&gt; - captures the message’s offset within the current batch. It’s helpful when storing multiple messages together and reconstructing their offsets later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;timestamp&lt;/em&gt; - records when the message was created or appended, useful for debugging or potential time-based handling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;payload&lt;/em&gt; - holds the actual binary data (the message’s content). Since Kafka treats messages as raw bytes, our code does the same.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The segment will look:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Segment&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  fileFd&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  filePath&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  baseOffset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  nextOffset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  startPos&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;fileFd -&lt;/em&gt; is the open file descriptor for writing to disk.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;filePath - is which file we’re actually appending to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;baseOffset -&lt;/em&gt; marks the global offset of the first record in this segment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;startPos&lt;/em&gt; tracks how many bytes we’ve already written so we know where to append the next batch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;nextOffset&lt;/em&gt; tells us which global offset for that segment that we’ll assign next if we keep writing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;partial-writes-and-cyclic-redundancy-check-crc&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#partial-writes-and-cyclic-redundancy-check-crc&quot; aria-label=&quot;partial writes and cyclic redundancy check crc permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Partial Writes and Cyclic Redundancy Check (CRC)&lt;/h3&gt;
&lt;p&gt;Even if data is fully on disk (via OS flush or &lt;em&gt;fsync&lt;/em&gt;), you can get only a partial batch if a crash hits mid-write. Kafka includes a CRC in the batch header. On startup, Kafka reads the tail of the active segment; if it can’t parse a full batch or the CRC fails, it truncates that partial data. This ensures that incomplete writing never pollutes valid messages.&lt;/p&gt;
&lt;p&gt;We’ll make a simplified approach: embed a small “magic byte,” a 4-byte CRC, and a record count (plus the compressed data). If we can’t read the entire batch or the CRC doesn’t match on startup, we truncate it. Let’s see how we code that “WAL” concept in segments.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;computeCRC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;buf&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Real Kafka: specialized CRC32C. We&apos;ll do MD5 =&gt; integer&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; md5 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createHash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;md5&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;buf&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;hex&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;md5&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;compressData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;buf&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Buffer &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; buf&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// no-op&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;building-the-batches-magic-crc-count&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#building-the-batches-magic-crc-count&quot; aria-label=&quot;building the batches magic crc count permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Building the Batches (Magic, CRC, Count)&lt;/h3&gt;
&lt;p&gt;We define a function to turn an array of SingleRecord into a single binary chunk. Real Kafka also includes a “batch length,” “timestamp type,” “attributes,” etc. We keep it minimal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The flow looks as follows:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;For each record, we construct the binary with offset, timestamp, and data length and append it to the binary buffer together with the payload.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We’re merging that into a single buffer and compress it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We’re building the batch header with magic number, CRC number, batch records count, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Returning the merged batch buffer with header and data.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;See:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildBatchBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;records&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SingleRecord&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Buffer &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rawParts&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; r &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; records&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 1. Build record header for each record&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// [ relativeOffset(4), timestamp(8), payloadLen(4), payload... ]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; recHeader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    recHeader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeInt32BE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;relativeOffset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    recHeader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeBigInt64BE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;BigInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    recHeader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeInt32BE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;payload&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    rawParts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;recHeader&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;payload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 2. Merge record into batch&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rawRecordsBuf &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rawParts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Compress it&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; compressed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;compressData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rawRecordsBuf&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; recordCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; records&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 3. Build the batch header&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// [ magic(1), CRC(4), recordCount(4), data... ]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; header &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;alloc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  header&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeUInt8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// say magic=2&lt;/span&gt;
  header&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeInt32BE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// placeholder for CRC&lt;/span&gt;
  header&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeInt32BE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;recordCount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; partial &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;header&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; compressed&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; c &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;computeCRC&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;partial&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  header&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeInt32BE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;c&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// 4. Return the buffer containing&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// batch header and compressed records&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Buffer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;concat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;header&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; compressed&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;the-segmented-log&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-segmented-log&quot; aria-label=&quot;the segmented log permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Segmented Log&lt;/h3&gt;
&lt;p&gt;Now we define the class that orchestrates everything: multiple segments, rolling, optional &lt;em&gt;fsync&lt;/em&gt;, etc. We’ll also add a &lt;code class=&quot;language-text&quot;&gt;recoverOnStartup()&lt;/code&gt; method next to handle partial writes. First, the skeleton:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SegmentedPartitionLog&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; segments&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Segment&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; activeSegment&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Segment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; currentOffset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; maxSegmentBytes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;512&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; doFsync &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; partitionId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; baseDir&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;existsSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseDir&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mkdirSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseDir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; recursive&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createSegment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;appendBatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;records&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SingleRecord&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// We&apos;ll fill in&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recoverOnStartup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// We&apos;ll fill in&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;rollSegment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// We&apos;ll fill in&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createSegment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseOffset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// We&apos;ll fill in&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We assume we only do size-based rolling. Real Kafka is also time-based.&lt;/p&gt;
&lt;h4 id=&quot;step-31-creatingrolling-segments&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#step-31-creatingrolling-segments&quot; aria-label=&quot;step 31 creatingrolling segments permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Step 3.1: Creating/Rolling Segments&lt;/h4&gt;
&lt;p&gt;When creating a segment, we open the segment file for reading and writing (so &lt;em&gt;a+)&lt;/em&gt;. The data being written will be inserted at the end of the file. If the file doesn’t exist, it’ll be created. We track the base offset.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;createSegment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseOffset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;baseDir&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;partition-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;partitionId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;baseOffset&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.log&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fd &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;openSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;filePath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;a+&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; seg&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Segment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    fileFd&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; fd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    filePath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    baseOffset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    nextOffset&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; baseOffset&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    startPos&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fstatSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fd&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;size&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;segments&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;seg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeSegment &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; seg&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentOffset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; baseOffset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If the active segment surpasses maximum segment bytes, we close it, and create a new one with &lt;em&gt;rollSegment&lt;/em&gt; method:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;private rollSegment(): void {
  fs.closeSync(this.activeSegment.fileFd);
  this.createSegment(this.currentOffset);
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;appending-batches-including-fsync&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#appending-batches-including-fsync&quot; aria-label=&quot;appending batches including fsync permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Appending Batches (Including &lt;code class=&quot;language-text&quot;&gt;fsync&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;When a batch of records is ready to be appended, we:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check for Empty Batch&lt;/strong&gt;: If there are no records, exit early.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check Segment Size&lt;/strong&gt;: If the active segment is full, roll over to a new segment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Assign Base Offset&lt;/strong&gt;: Note the starting offset for this batch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Serialize and Prepare Batch&lt;/strong&gt;: Convert records to a binary format suitable for disk storage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Write to Disk&lt;/strong&gt;: Append the serialized batch to the active segment’s file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optional Durability&lt;/strong&gt;: Flush the data to disk if &lt;em&gt;fsync&lt;/em&gt; is enabled. Instead of relying solely on fsync, Kafka ensures durability by replicating data across multiple brokers. This approach balances performance and reliability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Update State&lt;/strong&gt;: Move the file position forward and update offsets.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Assign Offsets&lt;/strong&gt;: Allocate and return unique offsets for each record in the batch. Kafka meticulously tracks offsets to maintain message order and ensure accurate consumption.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;appendBatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;records&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SingleRecord&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;records&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeSegment&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startPos &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxSegmentBytes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;rollSegment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; baseOffset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentOffset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; batchBuf &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;buildBatchBuffer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;records&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; seg &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeSegment&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; filePos &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; seg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startPos&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;seg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fileFd&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; batchBuf&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; batchBuf&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; filePos&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;doFsync&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fsyncSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;seg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fileFd&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  seg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;startPos &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; batchBuf&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lastOffset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; baseOffset &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; records&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentOffset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lastOffset &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  seg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;nextOffset &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentOffset&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; offsets&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; records&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; i&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    offsets&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;baseOffset &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; offsets&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h3 id=&quot;crash-recovery-partial-batches&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#crash-recovery-partial-batches&quot; aria-label=&quot;crash recovery partial batches permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Crash Recovery: Partial Batches&lt;/h3&gt;
&lt;p&gt;What if the broker or OS crashes mid-write, leaving half a batch? On the restart, we parse the last segment’s tail, read the batch header (magic, CRC, record count, etc.), read the data, and verify the CRC.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If we can’t, we truncate.&lt;/strong&gt; This ensures incomplete data is never recognized as valid messages.&lt;/p&gt;
&lt;p&gt;The basic flow looks as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Identify the Last Segment&lt;/strong&gt;: Since only the last segment is likely to contain incomplete or corrupted data after a crash, the method focuses on it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Starting from the tail, check batches.&lt;/strong&gt; Do this by reading the header and validating it. Check the magic byte to ensure the batch format is correct. Then, based on the data length from the header, check batch completeness to ensure that the entire batch data is present. After that, compute the batch data’s CRC and compare it to the stored CRC in the header to ensure data integrity. If the batch is okay, stop processing; if not, go to the next batch.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If any of the above checks fail, truncate the log file in its current position, removing the corrupted or incomplete batch and any following data.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After successfully truncating the log, recalculate the current offset, ensuring that new records are appended correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Why do we need it? We need to recover from a broker crash to ensure data integrity. If we do not discard partial writes, we might interpret broken data as messages. The WAL approach says, “append, then check on startup; discard incomplete tails.”&lt;/p&gt;
&lt;p&gt;I’ll skip the implementation for brevity; you can try implementing it, or I can expand it in the next article.&lt;/p&gt;
&lt;p&gt;Kafka incorporates additional layers of complexity and robustness, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;advanced metadata in batch headers,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;efficient CRC implementations,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;time-based segment rolling,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;comprehensive indexing for data retrieval and replication across multiple brokers for enhanced durability and fault tolerance,&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These features collectively enable Kafka to handle high-throughput, distributed, and resilient messaging scenarios beyond the simplified model presented.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;tldr-on-the-flow&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr-on-the-flow&quot; aria-label=&quot;tldr on the flow permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR on the flow&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/88dffce2fb08767f0f53faddcc9f877c/73ca2/2025-01-13-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 78.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAACcElEQVR42o2Uy27UMBSG5/3fhrbQBQtKJQRiMVwkkCohhnaaiXN1fLczlX5+O2lV6GYWR7aS4+/cz0ZPMxphMfQRwUY8JIfoA/pWoRVjOc3kMIdFrPLoGolpMEjew5mEsQ8YhwjP9xutEvouYBpneDdTyRWwkonfPOQYYPWMY0wERt4jjSv+tyswQhImRwJNwOYYLKKjZd1B9Tc43H2D2H/H1N2gFz/h1Q5e72CnHaL9g4fYMIpIA754fFw9fzw3cwEGKlc46rcw3SVU+6ZIf38B1eT7ZRE/XMKNn6BGSW80AX6NyJVzBbriYf6ZQ8qhHRMlzsxd5LdUviUfGZ4rsKHtmMtpebMCF8Y/wJfiteJjwcLUOOxvy91pSeiAVCLLhVlSlk4BRmvL+RhOfpzFG8Mq92jrqhiRXf+kewJwkQzK3x6SL+FqOdBzUYB2mk7x0MNIw/aYmEuNTrRsI0mYIWBpmdkveiflsISmRKl+tAcEUxF0h3r/g4ZuqaPWQrqXOcw5WvrKr/la7l79wthsMdRbSLHl/Qv6w2eo7iuN9ISEAgrmGfAYXekpqyQ7nhVtKoxtxRy1cMxVdPpJUhZ6HqwvgGw8y/MIi4fBZiWFqD5AtxcY6zNM4hVkfU7PzijnmJrX8OMZZrcjhGO4Fun/lG2WKrJ6MTKM3xy5jxy9KwziGm11RXlPeYeuuoaVW+pqjll4GrXnbVWAjsM+9pwAyW3jfEl2rmQjDEStyzn2uXlzEdh/XBqtcOt24si6vEgcDLdQHuGN1Qldy0dDKFsm5zS4RJhHfTCoa8dNkpai+bx9ZhyqbNCXdRUIyUAlfTHwF1SCyFAa2lXGAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/88dffce2fb08767f0f53faddcc9f877c/507e6/2025-01-13-cover.webp 200w,
/static/88dffce2fb08767f0f53faddcc9f877c/28a80/2025-01-13-cover.webp 400w,
/static/88dffce2fb08767f0f53faddcc9f877c/8d2ea/2025-01-13-cover.webp 800w,
/static/88dffce2fb08767f0f53faddcc9f877c/a6570/2025-01-13-cover.webp 1007w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/88dffce2fb08767f0f53faddcc9f877c/36ca5/2025-01-13-cover.png 200w,
/static/88dffce2fb08767f0f53faddcc9f877c/a3397/2025-01-13-cover.png 400w,
/static/88dffce2fb08767f0f53faddcc9f877c/a331c/2025-01-13-cover.png 800w,
/static/88dffce2fb08767f0f53faddcc9f877c/73ca2/2025-01-13-cover.png 1007w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/88dffce2fb08767f0f53faddcc9f877c/a331c/2025-01-13-cover.png&quot;
            alt=&quot;2025 01 13 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Producer&lt;/strong&gt;: Gathers messages, picks a partition, and forms a record batch in memory. Depending on settings like &lt;em&gt;batch.size, linger.ms&lt;/em&gt;, it ships that batch to the broker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Broker&lt;/strong&gt;: The partition leader receives the batch and appends it to the active segment in an append-only fashion. By default, it does not call fsync each time; it gets help from replication. Once enough replicas have the batch in their page caches; the leader returns an ACK to the producer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Crash&lt;/strong&gt;: If the broker crashes mid-write, on the restart, it checks the tail of the last segment, verifying each batch’s CRC and length. If incomplete, it &lt;strong&gt;truncates&lt;/strong&gt; those bytes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Retention&lt;/strong&gt;: Kafka eventually deletes older segments entirely if they exceed your retention window or log size limit. This is easy since each segment is a separate file.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&quot;conclusion-a-wal-like-architecture-for-distributed-streams&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion-a-wal-like-architecture-for-distributed-streams&quot; aria-label=&quot;conclusion a wal like architecture for distributed streams permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion: A WAL-Like Architecture for Distributed Streams&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;We built a dummy Kafka-like partition log design from a naive single-file approach to a segmented, CRC-checked one that can handle partial writes by truncation.&lt;/strong&gt; We also explained how &lt;em&gt;fsync&lt;/em&gt; ties in, why real Kafka typically doesn’t rely on it for each write, and how concurrency for fsync is managed safely.&lt;/p&gt;
&lt;p&gt;This approach lets Kafka scale to billions of messages while ensuring that once a message is acknowledged, it’s stored in multiple brokers, so partial or incomplete writes on one node’s disk can’t cause data corruption. Single-node dev scenarios might configure more frequent flushes, but that’s typically not done in multi-broker production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Either way, the WAL principle—append data, use checksums to detect partial tails, and optionally sync to disk if needed—is key to how Kafka ensures reliability and performance in a distributed environment.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Again, &lt;a href=&quot;/en/event_streaming_is_not_event_sourcing/&quot;&gt;that’s also why we should not treat Kafka as a database but as a messaging system.&lt;/a&gt; As you can see, it leans toward optimising throughput rather than consistency.&lt;/p&gt;
&lt;p&gt;I hope that helps you understand how Kafka and other messaging systems deal physically with getting proper delivery guarantees and performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please pass me your feedback if you’d like me to continue this implementation in the next few articles. I can explain the next elements of the pipeline or explore those topics further.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[How to build MongoDB Event Store]]></title><description><![CDATA[I have always said that MongoDB is not the best choice for event storage, and guess what? I just released the stable version of the MongoDB…]]></description><link>https://event-driven.io/en/mongodb_event_store/</link><guid isPermaLink="false">https://event-driven.io/en/mongodb_event_store/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/73ba4d0d67d570d0c57ecac006d1b9d6/b09c1/2025-01-10-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADKElEQVR42jWTy28bZRTF/QewQwgJiQ2CFSAB6oINNFAKQgKE2CAqkPhbEAu2sGgELs2jUBreIASVWqEoITFpidOME8fOjGfseXnGnvd4pnHS8ON6KhZHcz9pvvudc+85tTDNifK7xOU9kvKkQnpX6uKEMJsyiguCtKz+ifKSMJ0QZxOCOGMUJhXSvKCYTkmygprlj+mpu2g7v6Pv/UlPuYneuom2e4Nu8zp6t0mvvYllKLiujheG2L7PYDhk4A4xhx6tTof1xiZdXaOWxAl+7x/s+WexFl7Bunwes/4i5uXX6H8xh6/8Rl/XabXa2IZKHPgESSksRVVWUoiatY0tPvr4E5qtXWkYjhlZKurF84yWXiBffBzj8zl6l16nXz+H1/qDnj1mfeMW+sAm9kzK4pR0clQhL4/xxjHKXgfdHFCLghGB1cG9+CTZ8jOcLD3KyaWHmC4+xnH9QdL9H1A6A1bXRfbAoWU0+eWgQRTkOKNQmiV0ewOZZYY78qnFyQRf3SBdfprkxzdJvn+V7Ls50p/eIv/2LKG6hm0a2K5HMnS446jUt66jKBo31hqsCvMZlLYmDUfUiqNTkjQhFCmBbzP2LLxeE6ezjnu4iatuMeysMtYajOwDssCj9HWyaEg+KcmKKeXRseCeMBaGaT4lmg14ckwsdRDF2LdX6NVfYrDyAcbVCxjL79C/9j79lQ+x1G20wzaWZeMYbeI0EysVsqAj+o4tkqVZEE+IkoJImoViC3/nV4zPzmAuvi3bfoN0/mGchXPYX72HpSls3tqlcfsOmnooqtyKUJof4YidaokUkbww21gsForiGG/nZ/RPn6vmWiw9wb9fPoC3eBZj6V1xRIdtpcvGVhPXsYjGQwnBlGwyxfRcaShpiCQJsSCRl2Ys/a0rDOafJ796hvLrp5guPMLoysvY31xgqO/R3Vc4OOiIL7uiLKsY5sWxMPQkKZ4nbne5//WqBGjdbbqtNVRJiNpuoO5vcLj3VwVd28U4/Buzv4/pmMLKl3v3E6MN+uJDyWUsWfwfVVbltVS2VmW6wmlVV2cx8izn8WxEku3ZnURQ9UkL/gPuaY/O7GyeUwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/73ba4d0d67d570d0c57ecac006d1b9d6/507e6/2025-01-10-cover.webp 200w,
/static/73ba4d0d67d570d0c57ecac006d1b9d6/28a80/2025-01-10-cover.webp 400w,
/static/73ba4d0d67d570d0c57ecac006d1b9d6/0367d/2025-01-10-cover.webp 626w&quot;
              sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/73ba4d0d67d570d0c57ecac006d1b9d6/36ca5/2025-01-10-cover.png 200w,
/static/73ba4d0d67d570d0c57ecac006d1b9d6/a3397/2025-01-10-cover.png 400w,
/static/73ba4d0d67d570d0c57ecac006d1b9d6/b09c1/2025-01-10-cover.png 626w&quot;
            sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/73ba4d0d67d570d0c57ecac006d1b9d6/b09c1/2025-01-10-cover.png&quot;
            alt=&quot;2025 01 10 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I have always said that MongoDB is not the best choice for event storage, and guess what?&lt;/strong&gt; I &lt;a href=&quot;https://github.com/event-driven-io/emmett/releases/tag/0.23.0&quot;&gt;just released&lt;/a&gt; the stable version of the MongoDB event store in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And let me explain today how to do it.&lt;/strong&gt; It’ll be a detailed article, but I think it’s an interesting piece, so fasten your seat belts!&lt;/p&gt;
&lt;p&gt;A few things are needed for that. The first and most important thing is to have a motivated contributor!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I was lucky to have such with &lt;a href=&quot;https://www.linkedin.com/in/alexander-lay-calvert-2179501b4/&quot;&gt;Alexander Lay-Calvert&lt;/a&gt;.&lt;/strong&gt; He did most of the hard work; I was helping conceptually and with final touches. So, what you read today is a summary of our conjoined effort with a majority made by Alex.&lt;/p&gt;
&lt;p&gt;What else is needed? Let’s discuss that today!&lt;/p&gt;
&lt;h2 id=&quot;event-stores-as-key-value-stores&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#event-stores-as-key-value-stores&quot; aria-label=&quot;event stores as key value stores permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Event stores as key-value stores&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;In this article, we’ll use the canonical definition of event sourcing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Event Sourcing is about making decisions&lt;/strong&gt;, capturing their outcomes (so events) and using them to make further decisions (so events are the state). We do that by reading all the events we recorded already for the specific process/entity. We apply them and form in-memory decision model that we use to check our business rules, and deciding whether we record the next events.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Event Streaming is about moving information from one place to another&lt;/strong&gt; and integrating multiple components. Read more about in &lt;a href=&quot;/en/event_streaming_is_not_event_sourcing/&quot;&gt;Event Streaming is not Event Sourcing!&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Event stores are not messaging tools. They may have similar capabilities as Event Streaming solutions, but the focus is different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;event stores are focus on consistency, durability and quality of data,&lt;/li&gt;
&lt;li&gt;event streaming solutions (like Kafka) are focus on delivery, throughput and integration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;/en/event_stores_are_key_value_stores&quot;&gt;Event stores are key-value databases&lt;/a&gt;&lt;/strong&gt; At least logically. In relational databases, records are called rows; in document databases, documents; in Event Sourcing, they’re called streams.&lt;/p&gt;
&lt;p&gt;In event stores, the stream is built from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;the key&lt;/strong&gt; represents a record identifier (e.g. order id, invoice number, car license plate number, etc.),&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;the value&lt;/strong&gt; represents a sequence of business events that happened for the specific record (e.g. &lt;em&gt;OrderInitiated&lt;/em&gt;, &lt;em&gt;ShipmentScheduled&lt;/em&gt;, &lt;em&gt;OrderCompleted&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Simple as that, still, most of the time, we were taught that event stores are append-only logs looking more or less like that:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d7aa587db7ee507d3ae382f30dbfb671/a331c/append_only_log.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACIklEQVR42jVSWW/aQBD2r+5DHtKoaiJVUPWB0qpvvZSqVdKEw5jLJBCS0FwiDSlSVBQTQ7ABe7273l2fpGNQpdHszGg+zTffrES5j6mwMLMdl7jCj54Gmr797bvaaBZLlZp6+PHzV1mplcq1++GYsAARRpkgrucQIQEAYReQFqJ/B9q99tC9vlEqNaVcLVfV018XJUjqqqJUutc9aBg9mg5hmDBEhTRzhG75FosNHI5sf2xBKmweT0jgeAuoWDyeu7FBwhmNtJk3JZE+9wzkO4RL5t3ZTH720Mgaysak/HJYfWMq61o9M5XXhvWMqWyMK1t67fVj6bneyMyVtaH6dlJa16tpCxFpdNe9/fnisvCul0vd5NJXhWw//wrSP7mty0K2l09199O/5extPnWRz/b3N68K77s7m/3KB4SZhHlokCegZJLYpLFJogmOpzQyyGJGgS3ESdGki1URVhijwMS+AzvbmDnEdSjHhC+V4K4IvCDyg1gEEfeSmPshFwFcAlOW9FDuJBozAHPLgTux5FqYYdf7sbNXLFWPO+ft0/PWcad9ctY8Om21OwetE8txEeYIC1ALgWAW5oAEmA2TqQDLFeRPX7aLCpyq0Wx3qvXDvFxWD4529/JzBBw9RP6DiZs8YDA/oYA5EyEQBrZeGDMvXHkouiK0l39p5aFZavdnA22sjw3CPMJ8oI2S5XmiAhXYTbiAX5pHmU+TtuR7IcL/AWh2P7W/CJNVAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d7aa587db7ee507d3ae382f30dbfb671/507e6/append_only_log.webp 200w,
/static/d7aa587db7ee507d3ae382f30dbfb671/28a80/append_only_log.webp 400w,
/static/d7aa587db7ee507d3ae382f30dbfb671/8d2ea/append_only_log.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d7aa587db7ee507d3ae382f30dbfb671/36ca5/append_only_log.png 200w,
/static/d7aa587db7ee507d3ae382f30dbfb671/a3397/append_only_log.png 400w,
/static/d7aa587db7ee507d3ae382f30dbfb671/a331c/append_only_log.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d7aa587db7ee507d3ae382f30dbfb671/a331c/append_only_log.png&quot;
            alt=&quot;append only log&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Indeed, physically, most of them are append-only logs.&lt;/strong&gt; See: &lt;a href=&quot;/en/emmett_postgresql_event_store/&quot;&gt;Emmett PostgreSQL storage&lt;/a&gt;, &lt;a href=&quot;https://developers.eventstore.com/&quot;&gt;EventStoreDB&lt;/a&gt;, &lt;a href=&quot;https://martendb.io/&quot;&gt;Marten&lt;/a&gt;, &lt;a href=&quot;https://www.axoniq.io/products/axon-server&quot;&gt;Axon Server&lt;/a&gt;. But well, &lt;a href=&quot;/en/relational_databases_are_event_stores/&quot;&gt;the same can be said for the relational databases&lt;/a&gt;. Internally, each operation (INSERT/UPDATE/DELETE) is appended to the &lt;a href=&quot;/en/the-write-ahead-log-a-foundation/&quot;&gt;Write-Ahead/Transaction Log&lt;/a&gt;. Then, upon transaction commit, they’re applied to specific tables.&lt;/p&gt;
&lt;p&gt;I haven’t seen many (if at all) introductions to relational databases explaining them as append-only logs, and that’s fine, as it wouldn’t be accessible. We should also stop doing that for event stores.&lt;/p&gt;
&lt;p&gt;Again, even if internally, our event stores are append-only logs, then still each event is logically mapped to specific streams:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e11b637444800378d0652b4d345c604c/a331c/append_only_log_to_stream.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACZElEQVR42g1SXU/TYBTuP/TCO69M+Aea6JU3BjQC40YjEElUiJpgoMAQHPtic7a0W1u60W7d3Lq1++ravh99S7eyMHxPTk7OOcl5nvPFkNsYk6mPiA9JPL/PZHMHh0epi3Qy+atY4i4Lf7LZfC5fHA6dIIx8iCEiJIyoj3DIoCDCYYzDmQcCGN3ftPul8g0n189zfLok5zj1PC+kCoLtYhw/wNkCRAtqg3hBQwaRmW60tXqT3D2Y4knj+E0n87GZfNdKb9aTq1Tt1Gr9NFE9eK2waxq7Uj1evzlcrh2t1g6XKXOo6XWjaQRR3OLZ3M4LPbUls2tGelv49ko/f6+ebNDM9dF6I7OjHie039sSu14721TYBAMhqFTKulajKK3LvasPT8xqYRw99K7TfzYe96SzPprDMLYnGEbzPiT47n6AQjSbW2PIYARVRTIaGobuyNRNtTDsNgeDgdtvddVL1272bct3xzRj6XyVfVtPf6r8XGnzrGl2GB8AVZGNRgN4Dl0mItHIcdudHkABDG4hwt1udzKZ2INxS85Vdp9V9lfE3eeN/G7HtBjg+1JZ0GpV4I4810Fk2vn7o5R4ZJZPfRQAH1gDB+DQGjqOhygc9UFwS09l9UeM5wO5ItJifzJyXcfz4aAh6tnPtlHxALYNSU1/Na5O1czevyrnUnFGnudNrKZysccQjDmeVxQVQugC+gMBJDMSL2gLwXRhKSn9y9L195fC5tN2aR9PF5TMD6ZWvcxvLTFFzTFabTp9GIbdXo/neUmSyqIgy7KiKIVCkbsSShwvlCVJVkRRpNwBCQBAPsT/AQ9CJtuHbMRiAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/e11b637444800378d0652b4d345c604c/507e6/append_only_log_to_stream.webp 200w,
/static/e11b637444800378d0652b4d345c604c/28a80/append_only_log_to_stream.webp 400w,
/static/e11b637444800378d0652b4d345c604c/8d2ea/append_only_log_to_stream.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/e11b637444800378d0652b4d345c604c/36ca5/append_only_log_to_stream.png 200w,
/static/e11b637444800378d0652b4d345c604c/a3397/append_only_log_to_stream.png 400w,
/static/e11b637444800378d0652b4d345c604c/a331c/append_only_log_to_stream.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/e11b637444800378d0652b4d345c604c/a331c/append_only_log_to_stream.png&quot;
            alt=&quot;append only log mapping to stream&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;So again, logically, our event stores are key-value databases:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/86aef7ca8de30f19457d2894324231b3/a331c/key_value_database.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACcElEQVR42hWR60/TYBjF+5/62Q8mGMEYIiRqIghGowgaFMQFRtCZjW2wi+suDMZu7bp17cbGtnbXru3bvr2ym8zX5Hx4PpzfOXlyMMMa6+YIaDaA5ng6j8Qufru9YTzmOw8lU9l48hqPJ6OJq74ADHuKbFC3DXuCKGjcYVCzVKgpKhQBhFAr03QymUwkEn6/PxgM4XjU7/MFAuccxyOPJAMkGQBVAapmYPKw32rUu20OAoFv1Sq1pmHZ1t2daduaPUIOVTegbiqapUAd3apuAagjFEgDDEj9Wq3WbncUMGxRsSLubBB/qpeuJhnpEb52McpnvTwZbly7eSLI57xcxs0RwXb+rNcoI3iQSafoUgGl1PFd+uhx2bOe/r5QcG9kHIv06Wbmx2LRvZY6eEKerBZ+ruady8Tx89zeQ565wlRpQObTVbakSp1m/Cv9a6Xo2SAdC5T7DXWEsHXqaIl2rxWdS0XXi4xjiTp+JkuCAg2xz2Oy0M6mk2XULPZuwjvEwaNK+Asb2KrEDunzLSbqYALbtbijGtqpRPZYfL8a2ROFrixJQqeOyWI/k74sUaQ07Dfj38jdB51q2pzPtdFEn821yUyf3WvTv3A616f3qjmG5gQAWVEUsXuLoRgil2ZoSha4emy/dLJ8E/zAeVfYs/eMa7Xke4ceYf1vGdcK41kTB6hTBLIIgCL2GpgmC2T2kqELQFHqF4eU8ykT+MR6XlVC25TrJRv6zJ6+rgY/MqfrJc+m2OOGQv+/Bj2h28QuWPG21e50erppa9YEjuaSProi2HiKyJVqGYrN0/VssUIyt9Ce6aalG5ZmmCraHBr/AHvxHSUqaTW2AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/86aef7ca8de30f19457d2894324231b3/507e6/key_value_database.webp 200w,
/static/86aef7ca8de30f19457d2894324231b3/28a80/key_value_database.webp 400w,
/static/86aef7ca8de30f19457d2894324231b3/8d2ea/key_value_database.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/86aef7ca8de30f19457d2894324231b3/36ca5/key_value_database.png 200w,
/static/86aef7ca8de30f19457d2894324231b3/a3397/key_value_database.png 400w,
/static/86aef7ca8de30f19457d2894324231b3/a331c/key_value_database.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/86aef7ca8de30f19457d2894324231b3/a331c/key_value_database.png&quot;
            alt=&quot;key-value database&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;And that’s great, as it’s much easier to reason about the guarantees we should expect from our store after realising that.&lt;/p&gt;
&lt;p&gt;Plus, it allows us to implement event stores on top of &lt;em&gt;a bit more exotic&lt;/em&gt; storage like MongoDB, DynamoDB, CosmosDB or Blob Storage.&lt;/p&gt;
&lt;p&gt;Ok, enough intro! Let’s discuss how to do it using the MongoDB example!&lt;/p&gt;
&lt;h2 id=&quot;basic-event-stream-definition&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#basic-event-stream-definition&quot; aria-label=&quot;basic event stream definition permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Basic Event Stream Definition&lt;/h2&gt;
&lt;p&gt;Let’s try to make the definition of our event stream more formal. Formal? Yes, let’s create the code with type definitions to express our requirements more precisely. &lt;a href=&quot;/en/prototype_underestimated_design_skill/&quot;&gt;Prototyping is an underestimated design skill&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’ll use TypeScript because it’s a decent language for that. I won’t use much fancy structure, so it should be understandable. If not, paste it into the ChatGPT and translate it into your favourite language. That’s how we code today, aye?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We said that the event store is a key-value database and that records are called streams.&lt;/strong&gt; Let’s write that down then:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StreamName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventStore&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;StreamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Stream&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It reflects our knowledge; the shape of &lt;em&gt;Stream&lt;/em&gt; and type of &lt;em&gt;Stream Name&lt;/em&gt; are unknown to us, but we already know that &lt;em&gt;Event Store&lt;/em&gt; is a database, a map where &lt;em&gt;Stream Name&lt;/em&gt; is a key and &lt;em&gt;Stream&lt;/em&gt; is a value.&lt;/p&gt;
&lt;p&gt;Let’s now describe the Stream: it represents a sequence of business events that happened for a specific record.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StreamName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventStore&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;StreamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Stream&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Easy peasy, boom! We’re done!&lt;/p&gt;
&lt;p&gt;Ok, not quite. Let’s do a reality check with…&lt;/p&gt;
&lt;h2 id=&quot;requirements-for-event-store&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#requirements-for-event-store&quot; aria-label=&quot;requirements for event store permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Requirements for event store&lt;/h2&gt;
&lt;p&gt;What are the requirements, then? In my opinion, at least:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appending event at the end of the stream.&lt;/strong&gt; We need to be able to record new business facts for a specific entity,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reading all events from the stream.&lt;/strong&gt; That’s a must to &lt;a href=&quot;/en/how_to_get_the_current_entity_state_in_event_sourcing/&quot;&gt;get the current entity state from events&lt;/a&gt;. We read all recorded events and apply them to get the current state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The guarantee of the ordering within the stream.&lt;/strong&gt; It’s important to know the order of events in our process. Both in getting the state and integrating it with other workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Being able to read your writes.&lt;/strong&gt; As event stores are databases, you should get the new events right after you append them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strong-consistent, atomic writes and &lt;a href=&quot;/en/optimistic_concurrency_for_pessimistic_times/&quot;&gt;optimistic concurrency&lt;/a&gt;.&lt;/strong&gt; It’s a must to know whether we’re making a decision based on the latest state and whether conflicting changes have been detected. In other words, ensuring we won’t end up in the wrong state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Store both event data and metadata.&lt;/strong&gt; Event data is obvious; it gathers all information related to the business fact that happened. Metadata is needed for the generic handling, such as &lt;a href=&quot;/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/&quot;&gt;telemetry data&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The 2nd tier of event stores features (so great to have but not must-haves):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Being able to subscribe to notifications about newly appended events (best if it’s push-based). Also, having &lt;a href=&quot;/en/outbox_inbox_patterns_and_delivery_guarantees_explained/&quot;&gt;at-least once delivery guarantee&lt;/a&gt;.&lt;/strong&gt; That’s important for integrating different business workflows from different streams.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in projections for building read models&lt;/strong&gt;. We need to have performant queries. Reading all events from the stream is fine for business logic but not good enough for querying.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Global ordering of events.&lt;/strong&gt; It’s both useful for building read models from multiple streams and integrating workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;/en/gdpr_in_event_driven_architecture/#archive-data&quot;&gt;Streams archiving.&lt;/a&gt;&lt;/strong&gt; So, being able to move the obsolete events (e.g. from completed workflows) to some &lt;em&gt;cold storage&lt;/em&gt;. The fact that we record all business information doesn’t mean that we need to keep them forever.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We could expand our Event Store definition into:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventStore&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;appendToStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And show pseudo-code implementation as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; streams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;StreamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Stream&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appendToStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentEvents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; streams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    streams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;currentEvents&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; readStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; streams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    appendToStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    readStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, let’s validate how that fits into the MongoDB capabilities.&lt;/p&gt;
&lt;h2 id=&quot;mongodb-specifics&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#mongodb-specifics&quot; aria-label=&quot;mongodb specifics permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;MongoDB specifics&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/en/key-value-stores/&quot;&gt;Document databases are key-value databases whose values ​​have a defined structure&lt;/a&gt;. That is why they are called documents. We can compare them to paper applications we send to some government departments. Documents, just like paper ones, can hold various fields and types of data. Usually, they are stored as JSON-like objects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MongoDB is a document database. Documents are organised into collections, which are similar to tables in relational databases but without a fixed structure.&lt;/strong&gt; That means that each document in a collection can have a different set of fields, allowing for a more adaptable and dynamic data model. By storing related data in a single document, MongoDB reduces the need for complex joins, making data retrieval faster and simpler for many applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As I wrote in &lt;a href=&quot;/en/strategy_on_migrating_relational_data_to_document_based/&quot;&gt;my other article&lt;/a&gt;, contrary to common belief, document data is structured but just less rigidly.&lt;/strong&gt; We should define the schema. It should be denormalised, and it is best not to contain cross-document references. It can have relations but not be enforced strictly in a relational way.&lt;/p&gt;
&lt;p&gt;Of course, this lack of a fixed schema can also be seen as a downside when maintaining advanced data consistency and integrity, as there are fewer built-in constraints than in relational databases. Relational databases might be more suitable for applications that demand strict data relationships and complex transactions.&lt;/p&gt;
&lt;h2 id=&quot;storing-event-as-a-document&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#storing-event-as-a-document&quot; aria-label=&quot;storing event as a document permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Storing event as a document&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The first idea could be to store events as documents in the events collection.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/97f1103ef4ac2a4f57b4871a687b164b/a331c/event-per-document.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACMElEQVR42i1SzW7TQBD2A/QJuPAAHMqVA+/ACXGBR0BI3EDcQUJIrcQFuBQKvSFECS2p+hfS/yZNm5qGmsRxGidxvH8zu96105aybll9Gs2MZkbffLOO1BlII/XowG18mP60vrlTmC8uLC6tlNZK5U234S2vlj9/mS2VNyzmiou1AzcdnaMyQhoHZcogAZW2gt7iytqR5+8fNgpzC6vlrbniUmlt29rZ78XVnxvblQMb/vZ8Wwyo82YqFAMtVIZSIYCFkqi1RgREZIzbJwRIpROtU2M5ZhQMh0SgduwAUCPGGGGSCEWZiFlCmLBDqUgYByI0oXzYPR626yTqksGJreVoeN6sUsYFbe8m1YnEfS+9gtmbRG9eH06p+hQ0l039HXa2QAhlTpPR3yQ7A9rP6WDqyCQDAOLON59f+zMx7k3dqz0dC6bvui/Hg8kb/swD99lY5+tjv8fbnW7DazU8P+ic0Kgr1MixC1NKyPFK8OZWa+Z++O2R//pm+ONJ6+3t4OOdfuGh/+p6d+nF/lF7e7da2auvb1Uq1VoUtrnMHC5TEkd2GQCGUlrZpOSgDKIlpiw95BHnFAXRpxdJdp6eXSiIBaPCNlPQDE0chfHghA57LLboszikwzCXJ7KODftR8GvQrMb9YNgLaDwAleVq2xuATO2pKMdcZAbkPy59KnIwYfUlhF5eATmm9lQctFM6iptBrxsO7CeTZoRWvySzvjIWp3lSX9k8vMpg/ilTIc0/aklA9DdxqUUAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/97f1103ef4ac2a4f57b4871a687b164b/507e6/event-per-document.webp 200w,
/static/97f1103ef4ac2a4f57b4871a687b164b/28a80/event-per-document.webp 400w,
/static/97f1103ef4ac2a4f57b4871a687b164b/8d2ea/event-per-document.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/97f1103ef4ac2a4f57b4871a687b164b/36ca5/event-per-document.png 200w,
/static/97f1103ef4ac2a4f57b4871a687b164b/a3397/event-per-document.png 400w,
/static/97f1103ef4ac2a4f57b4871a687b164b/a331c/event-per-document.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/97f1103ef4ac2a4f57b4871a687b164b/a331c/event-per-document.png&quot;
            alt=&quot;Event per document&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;That could work, as each event type will help a different data set. You should expect different information in &lt;em&gt;EmployeeOnboardingCompleted&lt;/em&gt; and &lt;em&gt;RoomReservationRejected&lt;/em&gt;. MongoDB’s flexible schema would benefit us.&lt;/p&gt;
&lt;p&gt;When appending events, we would insert a new document. We could nest data and metadata inside the document payload.&lt;/p&gt;
&lt;p&gt;How would we read the events from the specific stream? We could keep the reference for the stream name in the event metadata. &lt;a href=&quot;https://www.mongodb.com/docs/manual/indexes/&quot;&gt;MongoDB allows indexing documents&lt;/a&gt;, so technically this could work.&lt;/p&gt;
&lt;p&gt;We could type our event as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Still, even with indexes, it’s not an optimised way to use MongoDB; we’ll be storing thousands or millions of events, and most of them will have different stream names. That means &lt;a href=&quot;https://docs.honeycomb.io/get-started/basics/observability/concepts/high-cardinality/&quot;&gt;high cardinality of values&lt;/a&gt;, which is not optimal for indexing.&lt;/p&gt;
&lt;p&gt;Also, how would we enforce the consistency and concurrency guarantees? How would we enforce optimistic concurrency so that when two conflicting events append, the first succeeds and the other fails? &lt;a href=&quot;https://www.mongodb.com/docs/manual/reference/method/db.collection.update/#std-label-update-query&quot;&gt;MongoDB allow conditional checks&lt;/a&gt; when storing new data, but only for a single document.&lt;/p&gt;
&lt;p&gt;Relational databases can handle that better, as they’re designed to handle such relations and multi-record transactions. So, for relational databases, a single table with events as rows works pretty well, but not here. See more in:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/gaoZdtQSOTo?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Storing an event as a document will not give us proper guarantees and performance, so it’s a no-go.&lt;/strong&gt; Document databases work best if we access them by document id. That’s how we can enforce consistency and concurrency and make it performant.&lt;/p&gt;
&lt;p&gt;What if we were…&lt;/p&gt;
&lt;h2 id=&quot;storing-event-stream-as-a-document&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#storing-event-stream-as-a-document&quot; aria-label=&quot;storing event stream as a document permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Storing event stream as a document&lt;/h2&gt;
&lt;p&gt;Let’s look again at our type definition:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StreamName&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What if we just stored the Stream as a document containing events? We could also store additional metadata like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;stream type (e.g. &lt;em&gt;Room Reservation&lt;/em&gt;, &lt;em&gt;User Onboarding&lt;/em&gt; etc.),&lt;/li&gt;
&lt;li&gt;stream position representing the number of events in the stream; this could also be used for the optimistic concurrency check,&lt;/li&gt;
&lt;li&gt;diagnostic information like when the stream was created, when it was last updated, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f04c597710690a2eb4cc9ae209a56d8d/a331c/stream-per-document.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACgklEQVR42hWSS0wTURSG79qtiZq4gpUu1IQ1mBgwMXEhJiZuJCFx4cK4NSaKCwMxohjBB6xFpUh4KEKhWB6lpbS0hU4tfU2ZlnZaZu7cuTN3nn3AeEn+xb/4v+Sc/xygmTVVszSzziTT499+bIV2FpY8K9619c2AL7CdTOXWNvwzc/M+f5DK7fHuMUm7fkx0i1KA6LasGqpuHxxWvOv+dI6LJ1IL7pUNf4hGfYGwe9k7v+jZ2NwORxnfVjjDFmlYJaaq2QBhXSa2otc1s0FFjJpZOzFrTep1q4GJSYzG6Wiablg1y7II0ZBqYtVUiAmwbmOsoFKCZ/6I6VWY8/Px3zAfqCaXxfRfKR/k934JXAxBUSwkYDkri1UklLBCMLGBjESEVSHyNfrybO5zW8H1INx3jnP1xIfa0sNXC1MPt56eKa0OYs1CUMB6A2t1WVGlakFRDYCFot50+Oh06NW1+MiN0s+e/YGLeVfv7vCt3fft+aknwb4WzjNomKZdP12HtmU3HU3FSDoCsMKRhlMKuWIDV1JjN82ZTucjwLPdFM58as9NPvI/b2Hdb1iuHIntZVkuEovvxJhslpWE0ims1JxyaGKvvzUz2mHPdVGYzN0JvetkPnQUpx8HX7QUV94e8mIimcmw3H6a/ZfK5fOcJPBAqHDYaPKRSaa/NT923ZrtckaAMXsbjV7AVDN31ddAXn1GG6bla1bTsI+thkMwlBECqqLIMhKi37NDl7jxe8Z89/EYIEu9J+Pn6xOXzcX7zheghQahrMNyhiYRVqBYhZUiJhbYTElsoXyQikEuKvFZegyxwMBqQTxMQJ6VeFYsJiShjGVIParkFHSEZZF+iEKs/y8PEXPDcJXtAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/f04c597710690a2eb4cc9ae209a56d8d/507e6/stream-per-document.webp 200w,
/static/f04c597710690a2eb4cc9ae209a56d8d/28a80/stream-per-document.webp 400w,
/static/f04c597710690a2eb4cc9ae209a56d8d/8d2ea/stream-per-document.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/f04c597710690a2eb4cc9ae209a56d8d/36ca5/stream-per-document.png 200w,
/static/f04c597710690a2eb4cc9ae209a56d8d/a3397/stream-per-document.png 400w,
/static/f04c597710690a2eb4cc9ae209a56d8d/a331c/stream-per-document.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/f04c597710690a2eb4cc9ae209a56d8d/a331c/stream-per-document.png&quot;
            alt=&quot;Stream per document&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Having that, we could redefine our stream type definition into:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Stream&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    streamType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    streamPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    updatedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s adjust our dummy Event Store implementation, as it’ll keep our focus on the design discussion without additional noise. No worries, we’ll get to the real MongoDB.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; streams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;StreamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Stream&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appendToStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 1. Get the current stream or set the default&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; streams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        streamType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 🤔 how to get it?&lt;/span&gt;
        createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        streamPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; streamExists &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;streamPosition &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 2. Append events&lt;/span&gt;
    stream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/// and update stream metadata&lt;/span&gt;
    stream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;streamPosition &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamExists&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;updatedAt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    streams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; readStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; streams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;events &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    appendToStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    readStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not surprisingly, we now need to use the nested structure. When appending events, we create a new stream if it doesn’t exist. Then, we append events to the nested collection and update metadata. That sounds good, but how do we get the stream type?&lt;/p&gt;
&lt;h2 id=&quot;stream-type&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#stream-type&quot; aria-label=&quot;stream type permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Stream Type&lt;/h2&gt;
&lt;p&gt;There are multiple ways to do it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;we could add additional options parameters that’d contain the name,&lt;/li&gt;
&lt;li&gt;if we’re using languages like C# or Java, we could add the generic parameter with type and get its name,&lt;/li&gt;
&lt;li&gt;we can change the first parameter to be an object containing stream name and stream type,&lt;/li&gt;
&lt;li&gt;we can use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Uniform_Resource_Name&quot;&gt;URN-bases&lt;/a&gt; format of Stream Name to include a stream type, e.g. “{streamType}:{streamId}“.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of them can be valid, but my personal preference is the last one. Why? Again, event stores are key-value databases. For them, &lt;a href=&quot;/en/using-s3-but-not-the-way-you-expected/&quot;&gt;the key to consistency is a proper key strategy&lt;/a&gt;.
We’d like to enforce our stream’s uniqueness for each type of stream. It’s okay if different stream types share the same id (e.g., the date when a reservation was made or an order was placed) as long as the combined stream name is unique.&lt;/p&gt;
&lt;p&gt;We could then redefine our stream name to:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StreamType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StreamName&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I use the nice TypeScript feature called &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html&quot;&gt;Literal Types&lt;/a&gt;. Essentially, it allows us to define the strongly typed keys of a certain format. Thanks to that, the TypeScript compiler won’t let us provide stream names that don’t follow our format.&lt;/p&gt;
&lt;p&gt;Now, we can add the function to parse our formatted stream name:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fromStreamName&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamNameParts&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; parts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; streamName&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;:&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    streamType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    streamId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; parts&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StreamNameParts&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  streamType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  streamId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We could use this function in our append method to get the stream type. Let’s skip that and focus on the final part, which is related directly to MongoDB.&lt;/p&gt;
&lt;h2 id=&quot;stream-id&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#stream-id&quot; aria-label=&quot;stream id permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Stream Id&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;If our stream name is a string of a specific format, potentially allowing any string as stream ID, then how would we fit into &lt;a href=&quot;https://www.mongodb.com/docs/manual/reference/bson-types/#std-label-objectid&quot;&gt;MongoDB ObjectID&lt;/a&gt;, which is used as document id?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;From MongoDB docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ObjectIds are small, likely unique, fast to generate, and ordered. ObjectId values are 12 bytes in length, consisting of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A 4-byte timestamp, representing the ObjectId’s creation, measured in seconds since the Unix epoch.&lt;/li&gt;
&lt;li&gt;A 5-byte random value generated once per process. This random value is unique to the machine and process.&lt;/li&gt;
&lt;li&gt;A 3-byte incrementing counter, initialised to a random value.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;ObjectId doesn’t allow the provision of any string, so we need to work around it. We can add the property with the stream name and index it. It won’t be perfect, but it will be fine for most cases.&lt;/p&gt;
&lt;p&gt;Our adjusted type definition will look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Stream&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  Type &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; StreamType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  Name &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamName&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; StreamName&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Name&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    streamId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    streamType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Type&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    streamPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    updatedAt&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see, this clearly shows that our stream has its type and the name derived from it.&lt;/p&gt;
&lt;p&gt;The updated dummy implementation will look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; streams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;StreamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Stream&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appendToStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StreamType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; streamId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; streamType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fromStreamName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 1. Get the current stream or set the default&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; streams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        streamId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        streamType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        createdAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        streamPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; streamExists &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;streamPosition &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 2. Append events&lt;/span&gt;
    stream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;/// and update stream metadata&lt;/span&gt;
    stream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;streamPosition &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamExists&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;updatedAt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    streams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; readStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; streams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;events &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    appendToStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    readStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re almost there: finally implementing it in MongoDB! The final decision remains to be made.&lt;/p&gt;
&lt;h2 id=&quot;streams-collection-layout&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#streams-collection-layout&quot; aria-label=&quot;streams collection layout permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Streams Collection layout&lt;/h2&gt;
&lt;p&gt;Ok, we already know what our Stream-as-a-Document representation should look like, and we’re feeling comfortable with it. Let’s discuss which collection we should store it in.&lt;/p&gt;
&lt;p&gt;The most obvious choice would be to have a single collection with all streams. That should work, as Mongo works well with even a large number of documents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, as we already know that streams have types, we could keep all the streams of the specific type in dedicated collections.&lt;/strong&gt; That should help to optimise stream usage, potentially apply precise indexing strategies, and make other optimisations.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c207e5bd4a5264c58f4f87b218e93231/a331c/collection-per-stream-type.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACbUlEQVR42hVRTW8SURSdH+BeE124dGFc1cRU002NGxMXxk1/i3HZnXFjDMXYhkhsbWygpaEtsUUEagotIAVaKMwHM8yb4b03b9578wnUxGlyFjc359zcc47A/Ql3J24wa19cJZPfMrvZ9M5u7ufhWa1R+9s+rbcue9LG5tav38eF4nG+UC7/qVDu+eE1cwKBexMDE4Ct9lU/vZ8rVWvls/p6aju1n8vsZbczW7t7O5up1MHh4VGxmMsflSoV1TQAgoR7gs18zRyPiQ2VBvj5FuSXR0fLRvG9mntndgua2FLES4wM7k8df+JPZv7kmnAfQKwjJDA3NAm1LIikmhh7Po4/1ONz4tpr+eMcPM92JaPevETG0KYOd6cWdcHYghazmGNg60asIwyhgeSGsjIfrN0PVu/aiUf80y3S3Kh35MppA481ypjNg/NOt1g+iTYGtEzLElx/ZliYME6lY+/HAk+/dFIvvL2lML1IB3lN01VtRPDYCf9RZyKrQB6OJGWkAhNgLFAnAAhZzCOUQTMKAhpqT23uac2s3jrQmxmjk4PDDgF9Gw4dlzvBzA2uMXVuPFMn1CFCNo8G6k0ZdzGQ5MSrQWyhH18UY0/E2FMt/6HV6TZbbbHXosyJmBaNPBPB5qFmmpAwyn3qhgQZxIJKcgl8mbcSj6eJ2/LnRb200hGNQrmiqUNKMPNm2OYAYcH1QoBv3o7u2RFshkZXytc3dPXBNHFvmrijxJ+B0spAUirVUzDs28xh7hQRNoJQKHdxqz/oK1IUG8Awqk3T+t2T7/LZhlZfV6pJuZpUe2VNPh+pF4apQZsaGGkmGBP6H3LaJMxthNOMAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/c207e5bd4a5264c58f4f87b218e93231/507e6/collection-per-stream-type.webp 200w,
/static/c207e5bd4a5264c58f4f87b218e93231/28a80/collection-per-stream-type.webp 400w,
/static/c207e5bd4a5264c58f4f87b218e93231/8d2ea/collection-per-stream-type.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/c207e5bd4a5264c58f4f87b218e93231/36ca5/collection-per-stream-type.png 200w,
/static/c207e5bd4a5264c58f4f87b218e93231/a3397/collection-per-stream-type.png 400w,
/static/c207e5bd4a5264c58f4f87b218e93231/a331c/collection-per-stream-type.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/c207e5bd4a5264c58f4f87b218e93231/a331c/collection-per-stream-type.png&quot;
            alt=&quot;Collection per stream type&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We could also allow custom collection resolution.&lt;/strong&gt; For instance, if you’d like to separate storage per application module.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/397fd920ceece792a38aa2e7ea7e869e/a331c/database-per-module.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACaklEQVR42hVRSW/TYBT0D+COhMqBWzlwoxJXDkj8DSQ4ovaMuIC4gYRUiIpQq7AIqorStMFd0oWqSttsjhNTJ6kdp47Xb7djx0mapguu9A7zNJoZzXtc1D8Le8NoMJLr6lzyK2GBKMk/5xfWM9tpfuMgV8we5CtSrShUl9P871Sa+d3+6fm1KhpytBNaCNkYNzRtZW3NRlCqySn+D5/ZWN3cXN/aWV7j9wu5YkU8LAkFUdRdx0YYUeZSypkAQuYrmlEoS9lcaW8/X67K8Rr0ziDzYl9AvKJYLYpSrihmDwsFoVKuHkEcU5ADlHlhFHSjekMpl8VSWVAUlWIUdvsxZWPkBz0/6NePm+WKJIhV7cRkQRSEAwsCzsEEuW20+ZKkn6LVScI/R9Ulq61iRwcEGwB4Ya87vOqNLqPRVe/8qju8CPujqH9+newil8C2Mf+ETt/CM+PW+zFYmJM0bOgtShybEt9nneaOJy9RmWdHKXaSh5biYcclhHNInKxj/pn/61GYetxdfEgr39VGDRgKoMQCDgKWvTKlJx64s/ftxD1zd7qmtnVNNc1mLKaYeUj45m6/snffgp03QPjhqHloqhZ0DddGwGwtTrmJO6fJMfvjeHvvs1TXWmrDsjUubgUxaGfe2Ym77uyE9mHCyrxuGF6jUTcNxUAYE+L+W4elJC0nnULSPc4iu+kR18GIgxQTxsjWi3Dm5umX2/1PN8KdSaVltZQaBEYbANbpBYNLv3cRT2dw2YmG8f3DaBR/m9uWDFnTGsVFU1wwq8u6MN+u/zV12bKaFgKQUZdghyBEKbrGCDCCPA8Q0rLN/zowI6QhRombAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/397fd920ceece792a38aa2e7ea7e869e/507e6/database-per-module.webp 200w,
/static/397fd920ceece792a38aa2e7ea7e869e/28a80/database-per-module.webp 400w,
/static/397fd920ceece792a38aa2e7ea7e869e/8d2ea/database-per-module.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/397fd920ceece792a38aa2e7ea7e869e/36ca5/database-per-module.png 200w,
/static/397fd920ceece792a38aa2e7ea7e869e/a3397/database-per-module.png 400w,
/static/397fd920ceece792a38aa2e7ea7e869e/a331c/database-per-module.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/397fd920ceece792a38aa2e7ea7e869e/a331c/database-per-module.png&quot;
            alt=&quot;Database per module&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Or separate storage for each customer?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a3a5e9ead6b3534a1030fe756df544fa/a331c/database-per-tenant.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACd0lEQVR42iWSy08TURTG+we4NjESdiYaN8bIWo0x8bH0H3CBK0PYGuPGx0YxLsQEFKrEF0J4CKXQlkqxCJbSzkzpY9rpzPTO3Dt37mOmtENbKmriEE++nN35fidfvkCrc+i1frUP/hRKSvDtBHebmWzu4+cvK9H4cmT1x1b6+8ZWLi+vJzd9xdeSTn2/0/3d7hw2W92A0/AQY75kTVtcWTEwFvK7C+FwKBJZXo2FY7HZ0OLmdmpHktKSlBKyBrYxdyxK/MOAYdu84anASu2IUqG8sZVOCznDYu3uX0iJSWy30clIhbSw6++fO2I2V7Ronbp7FuMB23HrzZbXahflSlYQslmhoigOp/utLqTU/6jV7hLGBVHyRxBFhGCz2XT3PERpwDegGNDYA77YT8ODbGmASXMQKBwD04aAEKeSpAt3ePguCQ2w6D1bzSKgMNvER2SKOQHG5G3n5Qk2ctp80UO2g5LKTKDbBJjY4OUkGD7fGOlFw6essQsIlIUSYJaGGflPrrGl/r2Zq978tf2Zy474QZELxKxCgg3KHDnCp29689e9mSte6BZSMlV5l2ID+WSLOcyp08x7HH+IEkN47QnOfLKUFIVVHQKDMBdrJPEYJZ5Ziad4fQjllm1NsC3gx3GUNqE2iD5Hr87gsT51uA9GH8lGvVwua3oBYMtBqjZ+g42drb25BF5f1KV4UWeqLGJqB6jDuFvnq/e90ePdid6D0WPet8GKBnWlBKHqW7uwzMfPHQR7Ou96O8GTvBAqyiqqyZiSQDxnFFRVTs+awpQpfa1lJkEpYdbyCFUNG0K/P5aGxCkzN68L04Y0h3TB0PM1s2ox/g+XGBQvvIZnawAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/a3a5e9ead6b3534a1030fe756df544fa/507e6/database-per-tenant.webp 200w,
/static/a3a5e9ead6b3534a1030fe756df544fa/28a80/database-per-tenant.webp 400w,
/static/a3a5e9ead6b3534a1030fe756df544fa/8d2ea/database-per-tenant.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/a3a5e9ead6b3534a1030fe756df544fa/36ca5/database-per-tenant.png 200w,
/static/a3a5e9ead6b3534a1030fe756df544fa/a3397/database-per-tenant.png 400w,
/static/a3a5e9ead6b3534a1030fe756df544fa/a331c/database-per-tenant.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/a3a5e9ead6b3534a1030fe756df544fa/a331c/database-per-tenant.png&quot;
            alt=&quot;Database per tenant&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Or maybe use a single database but combine the stream type with the customer name, or use another sharding strategy. Why not?&lt;/p&gt;
&lt;p&gt;Moreover, we can also allow users to select different Mongo databases for storage and logical separation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I recommend using collection per stream type as the default choice&lt;/strong&gt;, but two other approaches are also valid depending on the needs, so why not allow them all? Let’s do it!&lt;/p&gt;
&lt;p&gt;We’ll start by defining the options that users can provide to select our storage layout:&lt;/p&gt;
&lt;p&gt;Here are the single collection options:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CollectionPerStreamTypeStorageOptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;COLLECTION_PER_STREAM_TYPE&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  databaseName&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Collection per stream type:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SingleCollectionStorageOptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;SINGLE_COLLECTION&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  collectionName&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  databaseName&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And a bit more advanced custom resolution&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CustomStorageOptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CUSTOM&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  collectionFor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StreamType&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; CollectionResolution&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CollectionResolution&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
 databaseName&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  collectionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We allow the user to provide the function that will return the database name and collection name based on a defined algorithm.&lt;/p&gt;
&lt;p&gt;Let’s group it in the single union type marking that the need to provide one of those options:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StorageOptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; SingleCollectionStorageOptions
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; CollectionPerStreamTypeStorageOptions
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; CustomStorageOptions&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The collection resolution could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;resolveCollectionAndDatabase&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  streamType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StorageOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CollectionResolution &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;SINGLE_COLLECTION&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        collectionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;collectionName &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; defaultCollectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        databaseName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;databaseName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;COLLECTION_PER_STREAM_TYPE&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        collectionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        databaseName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;databaseName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CUSTOM&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collectionFor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamType&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultCollectionName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;streams&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I think that’s straightforward, but to recap:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;For single collection storage&lt;/strong&gt;, we resolve the collection name based on the provided value or the default name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;For collection per stream type&lt;/strong&gt;, we use the stream type name as the collection name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;For custom resolution&lt;/strong&gt;, we take whatever the user’s creativity gives us.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What would you say if we finally use MongoDB?&lt;/p&gt;
&lt;h2 id=&quot;implementing-mongodb-event-store&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#implementing-mongodb-event-store&quot; aria-label=&quot;implementing mongodb event store permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Implementing MongoDB event store&lt;/h2&gt;
&lt;p&gt;Let’s define the function that will provide us with the real MongoDB connection. It can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collectionFor&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  streamType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  client&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MongoClient&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StorageOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Collection&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Stream&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; collectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; databaseName &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;resolveCollectionAndDatabase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    streamType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; db &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;databaseName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Stream&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;collectionName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We use stream type and MongoDB client to resolve the database and return collection based on the storage resolution.&lt;/p&gt;
&lt;p&gt;Of course, we could add caching, automatic indexing on stream name, etc. &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt; does all of that, but let’s keep it out of this for brevity.&lt;/p&gt;
&lt;p&gt;With the collection resolution, we can update our event store implementation so that it is no longer dummy and use MongoDB.&lt;/p&gt;
&lt;p&gt;As we’ll be doing now a serious database operation, let’s change the Event Store definition into:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventStore&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;appendToStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Yes, I just changed the result types to return Promise, making our operations asynchronous.&lt;/p&gt;
&lt;p&gt;Now, let’s define our event store setup. We’ll need to take MongoDB client and storage options.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MongoDBEventStoreOptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  client&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MongoClient&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  storage&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StorageOptions&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MongoDBEventStoreOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; storage &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// (...) the rest of the code. We&apos;ll get here&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, we assume that the injected client is connected. Of course, that’s a simplified version; in &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;, we support passing clients in any state, providing the connection string, and handling the client lifetime internally.&lt;/p&gt;
&lt;h3 id=&quot;reading-stream-from-mongodb-document&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#reading-stream-from-mongodb-document&quot; aria-label=&quot;reading stream from mongodb document permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Reading stream from MongoDB document&lt;/h3&gt;
&lt;p&gt;Let’s see what our updated read stream will look like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MongoDBEventStoreOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; storage &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; readStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; streamType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fromStreamName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Resolve collection&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collectionFor&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; storage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Read events from the stream document&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;findOne&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;WithId&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Pick&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Stream&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;events&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $eq&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamName &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// just reading events&lt;/span&gt;
        projection&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; stream&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;events &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// (...) appendToStream&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    appendToStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    readStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you can see, it looks the same as our dummy implementation. The difference is that we’re using the MongoDB API. As we just want to read events, we can benefit from &lt;a href=&quot;https://www.mongodb.com/docs/manual/reference/operator/projection/positional/&quot;&gt;projection&lt;/a&gt; that allows us to select only events without all stream metadata. It is a small but nice performance improvement.&lt;/p&gt;
&lt;h3 id=&quot;appending-to-the-stream-in-the-mongodb-document&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#appending-to-the-stream-in-the-mongodb-document&quot; aria-label=&quot;appending to the stream in the mongodb document permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Appending to the stream in the MongoDB document&lt;/h3&gt;
&lt;p&gt;Let’s now implement our append method! It’ll look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MongoDBEventStoreOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; storage &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appendToStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; streamId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; streamType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fromStreamName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Resolve collection&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collectionFor&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; storage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Append events upserting the document&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $eq&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamName &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// append events&lt;/span&gt;
        $push&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $each&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; events &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// increment stream position&lt;/span&gt;
        $inc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.streamPosition&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// set default metadata&lt;/span&gt;
        $setOnInsert&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.streamId&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.streamType&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.createdAt&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// update metadata&lt;/span&gt;
        $set&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.updatedAt&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; upsert&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// (...) readStream&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    appendToStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    readStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Again, logically it works as our dummy implementation. The difference is in using MongoDB API. As we make append-only changes, we don’t need to read anything from the stream.&lt;/p&gt;
&lt;p&gt;We’re using &lt;a href=&quot;https://www.mongodb.com/docs/manual/reference/method/db.collection.updateOne/&quot;&gt;updateOne&lt;/a&gt; to do an upsert. MongoDB has a nice way of making atomic updates, such as appending to an array or incrementing a value. Thanks to that all of those changes will be applied to our document as a single atomic operation.&lt;/p&gt;
&lt;h3 id=&quot;optimistic-concurrency&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#optimistic-concurrency&quot; aria-label=&quot;optimistic concurrency permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Optimistic Concurrency&lt;/h3&gt;
&lt;p&gt;The final bit to provide the proper consistency guarantees is optimistic concurrency. You can read my &lt;a href=&quot;/en/optimistic_concurrency_for_pessimistic_times/&quot;&gt;general introduction&lt;/a&gt; and &lt;a href=&quot;/en/how_to_use_etag_header_for_optimistic_concurrency/&quot;&gt;technical implementation guidance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We want to detect conflicting updates and race conditions. In other words, it handles concurrency correctly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimistic concurrency is called optimistic, as we assume that conflict situations will be rare.&lt;/strong&gt; A conflict arises when two users try to change the same record at the same time. In our case, that means appending an event to a stream. When this happens, we will only allow the first user to update the state. All other appends will be rejected.&lt;/p&gt;
&lt;p&gt;For detection of whether the conflicting change was made, we’ll use a stream version as it is incremented with each append.&lt;/p&gt;
&lt;p&gt;What does an optimistic concurrency implementation look like?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Return the current stream position while reading events.&lt;/li&gt;
&lt;li&gt;Append the event, sending the current position as the expected one.&lt;/li&gt;
&lt;li&gt;Check if the position in the stream equals the expected position sent in the append operation.&lt;/li&gt;
&lt;li&gt;If they match, allow appending and set a new entity stream position.&lt;/li&gt;
&lt;li&gt;If not, throw or return an error.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To be able to do it, we need to modify our event store definition:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventStore&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;readStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ReadStreamResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;appendToStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AppendToStreamOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;AppendToStreamResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReadStreamResult&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; currentStreamPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppendToStreamOptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; expectedStreamPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AppendToStreamResult&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; nextStreamPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We added result types and extended them with the expected stream position information. We also added an optional parameter to provide the expected position during the append operation.&lt;/p&gt;
&lt;p&gt;Let’s now apply that to our read stream method:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MongoDBEventStoreOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; storage &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; readStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ReadStreamResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; streamType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fromStreamName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Resolve collection&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collectionFor&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; storage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Read events from the stream document&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;findOne&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
      WithId&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Pick&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Stream&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;events&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;metadata&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $eq&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamName &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// just reading events&lt;/span&gt;
        projection&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.streamPosition&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; stream&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;events &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      currentStreamPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; stream&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;streamPosition &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// (...) appendToStream &lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    appendToStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    readStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not much has changed; we’re just reading the stream position and returning it (defaulting to 0).&lt;/p&gt;
&lt;p&gt;How would the append to stream method change?&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; MongoDBEventStoreOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; storage &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appendToStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; StreamType &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; StreamType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamName&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AppendToStreamOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;AppendToStreamResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; streamId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; streamType &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fromStreamName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Resolve collection&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collectionFor&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; storage&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; expectedStreamPosition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;expectedStreamPosition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 1.a. Use provided expected stream position, or...&lt;/span&gt;
      expectedStreamPosition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expectedStreamPosition&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 1.b. Get the current stream version&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;findOne&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
        WithId&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Pick&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Stream&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;metadata&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $eq&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamName &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;// just reading the stream position&lt;/span&gt;
          projection&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.streamPosition&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// and use it as the expected one&lt;/span&gt;
      expectedStreamPosition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentStream&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;streamPosition &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; nextStreamPosition &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; expectedStreamPosition &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 3. Append events upserting the document&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; updatedStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $eq&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamName &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.streamPosition&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; expectedStreamPosition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// append events&lt;/span&gt;
        $push&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $each&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; events &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// set default metadata&lt;/span&gt;
        $setOnInsert&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.streamId&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.streamType&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; streamType&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.createdAt&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// update metadata&lt;/span&gt;
        $set&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.streamPosition&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; nextStreamPosition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;metadata.updatedAt&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; upsert&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 3. In case of expected version mismatch, throw an error&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;updatedStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;upsertedCount &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Stream version doesn&apos;t match&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; nextStreamPosition &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    appendToStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    readStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A bit more is happening here. After we resolve the collection by the stream type, we either take the provided expected position from options (if it was provided) as the current one or read it from the stream document.&lt;/p&gt;
&lt;p&gt;Once we have it, we pass it on to the update statement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If the document with a specified stream name and position wasn’t found, that means the stream doesn’t exist or the stream position is different.&lt;/strong&gt; Even though MongoDB will try to insert the document, it’ll fail as we have the unique index constraint on the stream name. If no document was upserted, then that means that there was an optimistic concurrency conflict, and we can throw an error.&lt;/p&gt;
&lt;p&gt;A bit more is happening here. After we resolve the collection by the stream type, we either take the provided expected position from options (if it was provided) as the current one or read it from the stream document.&lt;/p&gt;
&lt;p&gt;Once we have it, we pass it on to the update statement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If the document with a specified stream name and position wasn’t found, that means the stream doesn’t exist or the stream position is different.&lt;/strong&gt; Even though MongoDB will try to insert the document, it’ll fail as we have the unique index constraint on the stream name. If no document was upserted, then that means that there was an optimistic concurrency conflict, and we can throw an error.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Together with atomic updates, that gives strong guarantees to our MongoDB event store.&lt;/strong&gt; Of course, we should consider adding retries in case of concurrency error, but you can read about it &lt;a href=&quot;/en/idempotent_command_handling/#idempotency-and-optimistic-concurrency&quot;&gt;in my other article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;tradeoffs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tradeoffs&quot; aria-label=&quot;tradeoffs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Tradeoffs&lt;/h2&gt;
&lt;p&gt;While we showed that it’s perfectly doable to build an event store on top of MongoDB, there are some caveats worth highlighting.&lt;/p&gt;
&lt;p&gt;First, MongoDB’s atomic updates and transactions work best on single documents, so if you rely on cross-document or multi-stream consistency, you’ll need to coordinate or accept weaker guarantees carefully. That’s not a huge issue for appends, as we should keep our event streams denormalised and focused on business process. See more in my talk:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/gG6DGmYKk4I?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, that becomes an issue for updating read models: no global ordering out of the box.&lt;/strong&gt; Our single-document-per-stream approach ensures ordering within one stream, but nothing enforces a globally monotonic event sequence across all streams. You could try to simulate a global ordering by tapping into MongoDB’s &lt;a href=&quot;https://www.mongodb.com/docs/manual/core/replica-set-oplog/&quot;&gt;oplog&lt;/a&gt; or &lt;a href=&quot;https://www.mongodb.com/docs/manual/changeStreams/&quot;&gt;Change Streams&lt;/a&gt;, but it’s still more complex (and less straightforward) than dedicated event stores that offer a built-in global offset.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In Emmett, we provided the option to have &lt;a href=&quot;https://github.com/event-driven-io/emmett/blob/e8e0b3c8f9620dc42c4888f9dccbf4fd3e69d384/src/packages/emmett-mongodb/src/eventStore/projections/mongoDBInlineProjection.ts#L147&quot;&gt;&lt;em&gt;inline&lt;/em&gt; projections&lt;/a&gt; stored in the stream document, together with events.&lt;/strong&gt; That allows atomic updates in the same operation as events append. I’ll expand on it in the follow-up post.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There’s also a valid concern &lt;a href=&quot;https://www.reddit.com/r/node/comments/1hy5n9t/comment/m6etazv&quot;&gt;raised by Robert Kawecki&lt;/a&gt;. The maximum size of the MongoDB document is 16MB.&lt;/strong&gt; This is, actually, more than the raw JSON size, as BSON used in MongoDB is a binary format. If we &lt;a href=&quot;/en/keep-your-streams-short-temporal-modelling-for-fast-reads-and-optimal-data-retention/&quot;&gt;keep our streams short&lt;/a&gt;, that should be sufficient for most cases. The stream per document will need to be chunked into multiple documents. The proposed structure could be expanded to include chunk numbers and setting a unique index on streamName and chunk number. That will allow moving events to the new document once the size is reached. We may also need to use &lt;a href=&quot;/en/snapshots-in-event-sourcing/&quot;&gt;snapshots&lt;/a&gt;. I’ll cover this &lt;em&gt;2nd-day issue&lt;/em&gt; in the dedicated blog article.&lt;/p&gt;
&lt;p&gt;Lastly, from a durability perspective, past &lt;a href=&quot;https://jepsen.io/analyses/mongodb-4.2.6&quot;&gt;Jepsen tests&lt;/a&gt; have flagged edge cases under certain configurations and failover scenarios, indicating you’ll want to pay close attention to cluster setup and operational practices. None of that rules MongoDB out—it just means that if you absolutely need a strict global ordering or bulletproof multi-document consistency, a specialized event store (or a fully ACID relational system) might be a better fit.&lt;/p&gt;
&lt;p&gt;Otherwise, if per-stream concurrency and ordering suffice, this MongoDB-based strategy can still be a solid choice.&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h2&gt;
&lt;p&gt;I’d still use &lt;a href=&quot;/en/emmett_postgresql_event_store/&quot;&gt;PostgreSQL event store&lt;/a&gt; as the default choice, but MongoDB implementation appeared surprisingly good.&lt;/p&gt;
&lt;p&gt;Despite MongoDB limitations, many real-world applications do not require global event ordering or the highest levels of transactional consistency. If your workloads are primarily focused on per-stream ordering and optimistic concurrency within a single document, the described MongoDB strategy can be surprisingly effective.&lt;/p&gt;
&lt;p&gt;You can achieve a decent event store solution without deploying an entirely new infrastructure by treating each stream as a document, using upserts for atomic event append operations, and relying on MongoDB’s flexible schema.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If MongoDB is already part of your tech stack and the outlined constraints are not deal-breakers, this approach can deliver a pragmatic, production-friendly solution that balances performance, simplicity, and developer familiarity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I hope that this guide showed you how to design a proper event store on top of a document and a key-value database. Still, even with that it requires additional work to make it properly performant, reliable and accessible.&lt;/p&gt;
&lt;p&gt;If you’re in the Node.js space, don’t reinvent the wheel; just run:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @event-driven-io/emmett-mongodb&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And use it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can also check the fully working WebApi sample using it.&lt;/strong&gt; You’ll find it &lt;a href=&quot;https://github.com/event-driven-io/emmett/tree/main/samples/webApi/expressjs-with-mongodb&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you’re not in Node.js land and want me to help you build it &lt;a href=&quot;mailto:oskar@event-driven.io&quot;&gt;contact me&lt;/a&gt;, I’ll try to help. You can have a look at &lt;a href=&quot;https://github.com/event-driven-io/emmett/tree/main/src/packages/emmett-mongodb/src/eventStore&quot;&gt;Emmet’s implementation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please also share with me your thoughts, and share the article with your friends if you enjoyed it!&lt;/strong&gt; That’d be much appreciated, as writing it took me a hellova time.&lt;/p&gt;
&lt;p&gt;If you liked this article, you may also find those interesting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/event_stores_are_key_value_stores&quot;&gt;Event stores are key-value databases!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/event_streaming_is_not_event_sourcing/&quot;&gt;Event Streaming is not Event Sourcing!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/lets_build_event_store_in_one_hour/&quot;&gt;Let’s build event store in one hour!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/using-s3-but-not-the-way-you-expected/&quot;&gt;Using S3 but not the way you expected. S3 as strongly consistent event store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/lets_talk_about_positions_in_event_stores/&quot;&gt;Let’s talk about positions in event stores&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Thoughts on Platforms, Core Teams, DORA Report and all that jazz]]></title><description><![CDATA[All the best in New Year! Here’s my summary of 2024: I survived! And that’s a success that I want to repeat in 2025. Right before Christmas…]]></description><link>https://event-driven.io/en/thoughts-on-platforms-core-teams/</link><guid isPermaLink="false">https://event-driven.io/en/thoughts-on-platforms-core-teams/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;All the best in New Year!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here’s my summary of 2024: I survived! And that’s a success that I want to repeat in 2025.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Right before Christmas, I wrote a longer thread on social media with my thoughts on the hot topic: platform teams. I see &lt;a href=&quot;/en/training/&quot;&gt;many of my clients&lt;/a&gt; trying to apply it, some succeeding, some more, but all struggling.&lt;/strong&gt; I’m not an expert in the internal platforms, but I was a part of the &lt;em&gt;core&lt;/em&gt; teams, shaping continuous integration and delivery processes, and I see many similarities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As my thoughts spin up some good discussion (see &lt;a href=&quot;https://www.linkedin.com/posts/oskardudycz_the-longer-we-work-on-some-code-the-more-activity-7276863940020621312-E7ne?utm_source=share&amp;#x26;utm_medium=member_desktop&quot;&gt;here&lt;/a&gt; or &lt;a href=&quot;https://bsky.app/profile/oskardudycz.bsky.social/post/3ldvhjrsws22b&quot;&gt;there&lt;/a&gt;), I decided to share it with you today. And that’s a free post, so if you enjoyed it&lt;/strong&gt;, feel free (pun intended) to share it with your friends!&lt;/p&gt;
&lt;h2 id=&quot;the-challenge&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-challenge&quot; aria-label=&quot;the challenge permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The challenge&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The longer we work on some code, the more polished it gets? Actually, it’s not always like that. Past me believed in shared reusable code.&lt;/strong&gt; Now, I’m looking at it from a different angle. Platforms, shared code, core libraries are many faces of the same issue. We try to build one tool to rule them all, which never works. At least for a longer time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We try to centralise the code and by that common understanding. Yet, we usually end up with a common misunderstanding.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Whether you call it a “platform team“ or a “core team” the nature of the struggle remains the same: both groups aim to provide foundational solutions across the organization. They often wrestle with the same problem—trying to anticipate every scenario in a single, central codebase or service. Over time, these monolithic solutions become harder to maintain, and the team maintaining them drifts away from the real-world issues developers face day-to-day. That’s how both platform and core teams can fall into the trap of delivering an all-in-one system that erodes under the weight of endless patches and shifting priorities.&lt;/p&gt;
&lt;h2 id=&quot;the-fallacy-of-the-one-to-rule-them-all&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-fallacy-of-the-one-to-rule-them-all&quot; aria-label=&quot;the fallacy of the one to rule them all permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The fallacy of the one to rule them all&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I believe that even in internal projects, we should focus more on small, focused tools rather than a monolithic platform.&lt;/strong&gt; Tools are built for something; they don’t need to fulfil every possible scenario but can instead focus on a specific area. And I don’t mean only technical tools; I’m also talking about business features for user-facing areas—think back offices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why does that matter?&lt;/strong&gt; For most of our business, building one massive internal solution won’t ever be a business per se. It can only be a priority when it truly provides direct business value.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s how we get to the counterintuitive part of&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;“the longer honed, the better.”&lt;/strong&gt;&lt;/em&gt; Those big core/platform components tend to be the most stable after one or two years when we already have a solid grasp of what we’re doing. Then, they slowly degrade as the need for rapid delivery of internal capabilities becomes a priority, and teams focus back on their domain.&lt;/p&gt;
&lt;p&gt;Don’t believe me? Check this diagram from &lt;a href=&quot;https://cloud.google.com/devops/state-of-devops&quot;&gt;DORA Accelerate State of DevOps report 2024&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d9f116c68a239f95e586a83a649dea78/b7a58/2025-01-06-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHUlEQVR42nVSO2sUURSen2AnWIithYhYpxAsBBEUFEVJqYi2ghJBk8JVMGsRJBDQIiikCWqhBotEEMMyEleyruvObjKPnfd79pVNgvh5z53Z0SVYHO4953x85/UJcegiChxuSeSNGOXCwGWvtyeX5lPMvzGhHfs86Lsm6M+BzKLA5UZ/Ig18J/WpAV7IQchiewg9x4BpqAg8ixPGWeWtbgjXMbH6pcyIbOxuJegmAcd02DvoRez1OekIoSo3oSlNPjKBfc+GbRmoVGu4Nf0Sx8cf4mZhHksfS1BUmeV0GLoGsVzB4+dvsLpWwTYjj7JOhZa6AWWzwTq0WVcRSmvruDezgLGrRRw4M4mjVwo4dG4KB89OYexaERfuzOHE9Sc4fKmA/afv49WyiF+DhK+FE2qcUILHdvh7p43p+XfYd3ICx8YfsQ5e47NYxodPImYXlnD+9hyOXH6AixPPMDm7iJkXb1GTmuh1wnyXApHJG3U2qgXsdnD36SJO3ShC/LqOPgMSmDrf6Sfw2E6rP+tIQgfb/YjvuR2PXl4wdYXvkK486MV4v1LCt+81RhDnlyZLR/LRbQf55enS/5UNXbsTBxxIsTDT5VBKQ11ySYWpfCiXS204cpIBXFuHrm2y8SV2RYXLyLFaUOUGbFPjf8vQuE/FyWgy8o2WzORj/yUcdkJBGp0kRFqjOPkN6QekepUVlHPdEZ6K+q6VkaWd/gEaMQ27Ogqp4QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d9f116c68a239f95e586a83a649dea78/507e6/2025-01-06-cover.webp 200w,
/static/d9f116c68a239f95e586a83a649dea78/28a80/2025-01-06-cover.webp 400w,
/static/d9f116c68a239f95e586a83a649dea78/8d2ea/2025-01-06-cover.webp 800w,
/static/d9f116c68a239f95e586a83a649dea78/fcc02/2025-01-06-cover.webp 924w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d9f116c68a239f95e586a83a649dea78/36ca5/2025-01-06-cover.png 200w,
/static/d9f116c68a239f95e586a83a649dea78/a3397/2025-01-06-cover.png 400w,
/static/d9f116c68a239f95e586a83a649dea78/a331c/2025-01-06-cover.png 800w,
/static/d9f116c68a239f95e586a83a649dea78/b7a58/2025-01-06-cover.png 924w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d9f116c68a239f95e586a83a649dea78/a331c/2025-01-06-cover.png&quot;
            alt=&quot;2025 01 06 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Organization performance change when using an internal developer platform vs the age of the platform. Source: &lt;a href=&quot;https://cloud.google.com/devops/state-of-devops&quot;&gt;DORA Accelerate State of DevOps report 2024&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Do you see the peak in 1-2 years? &lt;strong&gt;That’s when erosion starts.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When we add more features, we already get an understanding of what we do. Then, they slowly degrade as the need for rapid delivery of internal capabilities slows down and thus becomes a priority. Teams start to focus on their domain.&lt;/p&gt;
&lt;p&gt;Adding a quick patch to a stable core won’t seem like a big deal, but it piles up. Meanwhile, the tech landscape is changing. Better tooling emerges, and our homebrew solutions become obsolete. Add to that the fact that it’s hard to justify constantly working on a tech-focused platform that, according to management, “should have been already done.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then, the platform team often becomes detached from the daily struggles of other teams, and we start seeing Conway’s Law in action.&lt;/strong&gt; Not great. What does that mean, detached?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;they lose connection with what business features are delivered,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;they see just the narrowed internal view of the delivery process, losing the overall scope,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;they start to be seen as cost-only teams, as they have a hard time showing the exact impact of their internal, technical solutions for the benefit of the other teams.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you think that’s only my anecdotal evidence, check the interesting findings from the DORA report.&lt;/p&gt;
&lt;p&gt;It can bring the instability of the development process:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In the case of throughput, we saw approximately an 8% decrease when compared to those who don’t use a platform. We have hypotheses about what might be the underlying cause. (…)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In general, when an internal developer platform is being used to build and deliver software, there is usually an increase in the number of “handoffs” between systems and implicitly teams. (…)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Each of these handoffs is an opportunity for time to be introduced into the overall process resulting in a decrease in throughput, but a net increase in ability to get work done.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And on skewed perspective of the organisation about the platform team impact:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Interestingly, the impact on productivity of having a dedicated platform team was negligible for individuals. However, it resulted in a 6% gain in productivity at the team level. This finding is surprising because of its uneven impact, suggesting that having a dedicated platform team is useful to individuals, but the dedicated platform team is more impactful for teams overall.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;generic-does-not-mean-simple&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#generic-does-not-mean-simple&quot; aria-label=&quot;generic does not mean simple permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Generic does not mean Simple&lt;/h2&gt;
&lt;p&gt;That’s why I always say, &lt;a href=&quot;/en/generic_does_not_mean_simple/&quot;&gt;“Generic does not mean Simple”&lt;/a&gt;. Reusability and maintainability are not values per se.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;My suggestion is to focus on delivering precise business or tech tools.&lt;/strong&gt; You may not need to maintain them forever, and it can be simpler to justify and explain to management why you must keep them fresh. Of course, it’s less sexy than playing with the newest stack, but it pays off in the longer term.&lt;/p&gt;
&lt;p&gt;That’s what I was also doing with my OSS work. I have &lt;a href=&quot;https://github.com/oskardudycz&quot;&gt;Event Sourcing samples&lt;/a&gt; that I delivered in the form: &lt;em&gt;“Take them in whole or parts, adjust to your needs.” Then, observing how people interacted with them and seeing the usage patterns&lt;/em&gt; motivated me to pack them into the reusable toolbox like &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can do it in the same way in your regular projects. Of course, we need to be careful. Copy/pasted templates can help, but they’re also cumbersome if we need to update them later and replicate organisational changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s more about “catalogue how we do it” than “enforce how we must do it”, aiming to reduce the boring, repeatable setup.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Creating a generic internal platform from the very beginning of a project can introduce artificial limitations. We’re making those hard decisions at the worst possible moment: when we’re dumbest. We don’t know the tech stack or the domain; we’re shaping our product vision. We try to predict where and how the business and tech will evolve. Frankly, we’re more guessing than predicting.&lt;/p&gt;
&lt;p&gt;It’s often a sign of being too self-assured or short-sighted: it’s easy to whip up the solution that feels quick and friendly for a single case, but once we reach the long tail of features, we realize we didn’t anticipate everything.&lt;/p&gt;
&lt;p&gt;For example, rather than providing an all-encompassing core “everything module” that does automatic mapping, calls pluggable services, generates docs automatically, and so on, it’s more sustainable to offer smaller modules specifically focused on handling particular concerns. A huge internal platform must handle all possible scenarios, and maintainers must guess the usage. In contrast, smaller, dedicated solutions let teams solve specific use cases based on the patterns they want to promote.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;People can also diverge more easily and adopt new tech when they’re not locked into an all-encompassing internal platform.&lt;/strong&gt; It’s simpler to remove a small, now-obsolete piece of code than to tear out something massive. Plus, when multiple teams use those small modules, they can contribute improvements and provide feedback.&lt;/p&gt;
&lt;p&gt;Let’s quote the DORA report again:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A key factor in the success is to approach platform engineering with user-centeredness (users in the context of an internal developer platform are developers), developer independence, and a product mindset. This isn’t too surprising given that user centricity was identified as a key factor in improved organizational performance this year and in previous years. Without a user-centered approach, the platform will be more a hindrance rather than an aid. (…) Recall that in thecontext of the platform, users are internal engineering and development teams. (…)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;First, prioritize platform functionality that enables developer independence and self-service capabilities. When doing this, pay attention to the balance between exclusively requiring the platform to be used for all aspects of the application lifecycle, which could hinder developer independence.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;plaform-takes-the-pain&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#plaform-takes-the-pain&quot; aria-label=&quot;plaform takes the pain permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Plaform takes the pain&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I love the term&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;Platform Takes The Pain; it’s a great motto stated by Pia Nilsson, responsible for the&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;Spotify platform.&lt;/strong&gt; She explained it in &lt;a href=&quot;https://corecursive.com/platform-takes-the-pain/&quot;&gt;the Corecursive Podcast episode&lt;/a&gt;. She told how they transitioned from isolated teams into a centralised but focused platform. It’s a great story about transitioning the platform team mentality from playing with CI/CD tools and experimenting to becoming an enabling team for others.&lt;/p&gt;
&lt;p&gt;So yes, centralisation is not always bad. Some elements—like cross-service communication, documentation, or security—have to be enforced to maintain consistency, but for most things, gentle guidance is enough.&lt;/p&gt;
&lt;p&gt;Spotify realised that the platform should not be the elitist team enforcing others’ behaviour but helping them proactively, contributing hand in hand to introducing the new approach. That means making the actual Pull Requests to business modules modernising their way.&lt;/p&gt;
&lt;p&gt;That’s also a similar piece of advice that &lt;a href=&quot;/en/applying-observability-from-strategy/&quot;&gt;Hazel Weakly did in our webinar on Applying Observability&lt;/a&gt;. She told how to add observability and instrumentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;But the teams will never have time to do this. If you ask them to do it, it’ll take months, it’ll take quarters. So you have to just say, we’re going to grab a couple people full-time, run through, and have those people do absolutely one thing.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The manual instrumentation and the setup just over and over and over and over. They’re going to get sick of it, and they’re going to get tired of it, but they’re going to get really efficient at it. And they’re going to do some rip through the code base, make a whole bunch of pull requests, and do that.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;When that happens, when that’s through, when that’s done, you are good to go. (…) Once you set up that skeleton and developers can low effort, see the code and copy paste it and do their own thing, you are good to go. You are, it won’t start happening. You’ve lowered the barrier of entry. And that is the magic of observability is lowering the barrier of entry.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If it’s too high, people are never going to want to sit down and understand it. They need to almost accidentally be able to learn it rather than have to dedicate time on the weight.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Such an approach helps to set the priorities right, so enabling, not enforcing.&lt;/p&gt;
&lt;p&gt;I saw that platform teams believe they’re the foundational and elitist team. At the same time, they’re there to help others deliver business value. We need to avoid the story shared by &lt;a href=&quot;https://corecursive.com/stack-overflow/#core-team&quot;&gt;Ben Dumke-von der Ehe on his work in the Stack Overflow core team&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;It was the core Stack Overflow team, and I was on that team. And one day, our manager came into chat and said, “We’re thinking about renaming the core team to Q&amp;#x26;A team, because it has this favoritism thing that it kind of implies, in a way.” And I went… I don’t want to say I went apeshit, not that bad, but I was all up in arms and saying something like, “Are you accusing us elitism? Because we are not elitist. And we respect everybody, yada, yada.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;And couple years later, when I then read that back, I so wanted to smack my past self over the head, because it made zero sense.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;It wasn’t about us. It wasn’t about us that were being perceived as elitist or not. It was about the fact that everybody else was feeling second class, because, “That’s the core team, and we’re just the rest.” That was about them and not us.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have observed in the past that there’s a weird scenario. Platforms/core teams are often formed with the best technical people. In theory, they should form the best team, but in practice, it’s not like that.&lt;/p&gt;
&lt;p&gt;This is a dangerous mixture, as people in the core team are aware of those criteria and get (sub)consciously the elitism feeling. That creates tension between teams as, in theory, they’re the best but also highly paid and have a hard time showing the business benefits of their actions.&lt;/p&gt;
&lt;p&gt;The regular teams can see them as “why they’re considered best if we’re bringing income, and they’re only bringing costs.” As I mentioned in the article, the DORA report also shows that.&lt;/p&gt;
&lt;p&gt;That’s also why I like the phrase &lt;em&gt;“Platform takes the pain”&lt;/em&gt;, as it highlights that it should be a business-enabling team.&lt;/p&gt;
&lt;h2 id=&quot;pitfalls-and-solutions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pitfalls-and-solutions&quot; aria-label=&quot;pitfalls and solutions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pitfalls and solutions&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Part of the problem is also a mix of lack of trust and the mistaken assumption that centralizing everything will save development time overall.&lt;/strong&gt; Early on, it might—but soon, the Pareto principle hits, and we spend 80% of our time on the last tricky 20%. I’ve fallen into this trap myself, creating classes with 14 generic parameters or pipelines that claimed “zero config.”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This quickly becomes a vicious circle: by dictating everything from the centre, we don’t teach genuine ownership or a learning path.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’ve witnessed plenty of trouble arising from piling everything into one core. It might not be obvious initially, but after a year or two, a single, centralized repository of random code becomes an unmaintainable anchor. That’s not to say it can’t work—it’s just more likely to work with smaller teams that share an understanding of the domain. It’s also about how much knowledge each team has and how we foster continuous learning.&lt;/p&gt;
&lt;p&gt;A helpful tool might say: “If you stick to the recommended approach, you get OpenAPI for free. If you don’t, that’s fine, but then you own it and must maintain it.”&lt;/p&gt;
&lt;p&gt;Or another example: When building CI/CD pipelines, avoid magical, behind-the-scenes processes. Instead, provide plugins or templates with safe defaults. Infrastructure-as-Code tools like AWS CDK enable building well-defined components rather than black boxes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this way, we’re showing people benefits. We’re giving them a carrot, not only a stick, and the option to diverge if that makes sense. It also gives people ownership and makes them accountable.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As Charity Mayors say, you build it, you run it.&lt;/p&gt;
&lt;p&gt;She gave a nice talk on &lt;a href=&quot;https://www.infoq.com/presentations/platform-engineering-teams/&quot;&gt;Perils, Pitfalls and Pratfalls of Platform Engineering&lt;/a&gt;. She outlined the following pitfalls in her talk:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Running Too Much Software.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Writing Too Much Software.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Not Letting Product Teams Own Their Own Reliability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Not Giving Engineers Enough Tooling to Understand Their Code as Well as Operate It&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Being Confused About Who Your Customer Is.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Not Running Your Team Like a Product Team.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Not Paying Enough Attention to Cost and Spend as Part of Architecture and Planning&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Not Constantly Looking for Ways to Deprecate, Delete, and Shed Responsibilities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And that nicely wraps up what we should avoid and what we have also discussed so far.&lt;/p&gt;
&lt;h2 id=&quot;platform-as-a-back-office&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#platform-as-a-back-office&quot; aria-label=&quot;platform as a back office permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Platform as a back-office&lt;/h2&gt;
&lt;p&gt;I love the idea that Andreas Pinhammer explained in his talk: &lt;a href=&quot;https://www.youtube.com/watch?v=FzycqiJVioI&quot;&gt;DDD in large product portfolios&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A key takeaway was how their “platform team” became a business back-office focused on enabling others instead of just a “Kubernetes-and-other-technical-stuff” team.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;They focused on business features that are not user-facing but can help other business teams do things faster. A good example of that could be payment module-as-a-platform. Multiple modules can require payments, so there’s no point in repeating those features all around.&lt;/p&gt;
&lt;p&gt;We can gather the requirements and form a dedicated team responsible for delivering business capabilities to other teams.&lt;/p&gt;
&lt;p&gt;There are good tools that can help in that, like &lt;a href=&quot;https://github.com/ddd-crew/context-mapping&quot;&gt;Context Mapping&lt;/a&gt; and &lt;a href=&quot;https://teamtopologies.com/&quot;&gt;Team Topologies&lt;/a&gt;. You can start with those two talks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=k5i4sP9q2Lk&quot;&gt;Introduction to Context Mapping&lt;/a&gt; by Michael Plöd&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=haejb5rzKsM&quot;&gt;Monoliths vs Microservices is Missing the Point—Start with Team Cognitive Load - Team Topologies&lt;/a&gt; by Matthew Skelton and Manuel Pais,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ijKATZCQZL4&quot;&gt;Autonomy, is that what we really want?&lt;/a&gt; by Kenny Baas-Schwegler&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=eg0U21xs7c8&quot;&gt;Systems Thinking by combining Team Topologies with Context Maps&lt;/a&gt; by Michael Plöd&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Lfzph_5wb9c&quot;&gt;Architecture for Flow - Wardley Mapping, DDD, and Team Topologies&lt;/a&gt; by Susanne Kaiser&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In short, that’s also the impact of the Conway Law that we just cannot avoid. We discussed that in detail in:&lt;/p&gt;
&lt;p&gt;[&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 56px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC80lEQVR42o2VO0hbYRTHr4nvJ6VK1fiig7YIobo4ZDAWEgfpIg4+0aWDQ8DFki0FFReLLipOIopgfEDRRR0FBxdRR4cKVXAQI4nGmHu9/55zki9VWmM+OPnuvd/5fuf/Pc6JBsCiUbu8vHz38PDw1TAMbzQaHab+WzIjH9UPB4NBBzP8fr+Vey0UCtlp8BdizUTqzVT+JOa7phrB9uMOETI9HA7r3CezSCSinqOPj48GTyZhn7Wbm5v3BAyqiB6PBwMDA/KytbWFi4uL2IAZE06+0i8uLsLpdOLq6gpxMO7v739o1NfRPoT4w8bGhkmC0d3dLZOampqwvb39DKTregLIvkNDQzLMP7e3tzMaSa+j9d/xh/HxcQH29/fLpMHBQezvx3aDovM+4e5OXLGwsCBAl8uVUEhb9X9gb2/vM1VPm1I4Pz8vwLa2tpeBo6OjZnp6OmpqatDZ2SlgVsvPHR0d8tzX1ye93W5HdnY2WltbXwaOjIyYmZmZKC8vF2c2VsEB6uvrYbFYwONZWVkoLi5GQUEB3G53cmBGRgYqKytRXV2NoqIiTE5O4vT0FIeHh9jd3UVFRYUY++Tn578OZAUMy8nJQVdXF46Pj9HY2Ija2losLy9jenoavC1VVVWpA3mJvNTZ2Vl4vV6MjY3h6OgIDocDe3t7slRW+SqQD+Wpwp6eHlkqHwArXFpawtzcHHhblMJXD4UPgqOrg5mYmMDJyYko29nZQWlpKUpKSmCz2URp0ns4NTUl95CjM1Atq6GhAc3NzQIoKysTGB8K72V7e/s/wA90gSX1rq+vzZaWFlHGILbCwkJYrVakpaXJO0Pz8vKQm5srgQ8ODhKpR6wZjZLbRtUioIoDFQtsbm5iZWUFq6urYmtra1hfX0+8Kzs7O1MZpceBPlW+/PyBwDpVFSMeMSUz4vlJc8ME/ChAUvWGKs5P42/yplpkTS5rNO035fgXgfl8Pgv/DbAFAoFPNMAXy5WiuakKOc/Pz9+qv4A/x/GAJi+lEOYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/61627/image-2.webp 56w&quot;
              sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png 56w&quot;
            sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;Architecture Weekly&lt;/p&gt;
&lt;p&gt;Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/p&gt;
&lt;p&gt;Hey! 😀…&lt;/p&gt;
&lt;p&gt;Listen now&lt;/p&gt;
&lt;p&gt;2 years ago · 6 likes · Oskar Dudycz&lt;/p&gt;
&lt;p&gt;](&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees?utm_source=substack&amp;#x26;utm_campaign=post_embed&amp;#x26;utm_medium=web&quot;&gt;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees?utm_source=substack&amp;#x26;utm_campaign=post_embed&amp;#x26;utm_medium=web&lt;/a&gt;)&lt;/p&gt;
&lt;h2 id=&quot;final-advice&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#final-advice&quot; aria-label=&quot;final advice permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Final advice&lt;/h2&gt;
&lt;p&gt;If you’re already in the scenario where your platform/core is going rogue, I suggest focusing on delivering exact business instead of tech tools. You can start with the following steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Identify the “big internal platforms” or “core systems”&lt;/strong&gt; that have grown beyond their original intent. Then, start breaking them down into smaller, specialized capabilities and tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prioritise business capabilities over purely technical solutions.&lt;/strong&gt; No, Kubernetes is not a platform; it’s a technical tool that can help with this.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Enforce only critical aspects&lt;/strong&gt; (security, docs, cross-module communication) while letting teams shape the rest to their needs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Encourage solutions that teach&lt;/strong&gt;, offering safe defaults for most people while giving advanced users room to experiment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Involve multiple teams&lt;/strong&gt; in maintaining these smaller tools so everyone owns the improvements and shares what they have learned.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By that, we adapt by delivering smaller, targeted solutions, better tooling, and real synergy between operations, coding, and design. It’s all about enabling teams to be effective, accountable, and ready to adjust as our business grows and our tech evolves.&lt;/p&gt;
&lt;p&gt;Of course, it’s less sexy than playing with the new tech stack, but it will pay off in the longer term.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are your experiences, challenges, and pieces of advice? Please share them with me; I’m more than curious!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Locks, Queues and business workflows processing]]></title><description><![CDATA[Last week, we discussed Distributed Locking. Today, we’ll continue with locking but doing it differently: with a full backflip. We’ll…]]></description><link>https://event-driven.io/en/locks-queues-and-business-workflows/</link><guid isPermaLink="false">https://event-driven.io/en/locks-queues-and-business-workflows/</guid><content:encoded>&lt;p&gt;&lt;a href=&quot;/en/distributed-locking-a-practical-guide/&quot;&gt;Last week, we discussed Distributed Locking&lt;/a&gt;. Today, we’ll continue with locking but doing it differently: with a full backflip.&lt;/p&gt;
&lt;p&gt;We’ll discuss how to implement a simple in-process lock. What’s special about it? We’ll use the queue broker we delivered in &lt;a href=&quot;/en/ordering-grouping-and-consistency/&quot;&gt;Ordering, Grouping and Consistency in Messaging systems&lt;/a&gt;. And explain why. Then, we’ll take it back to a higher level, showing you how your business application can benefit from it.&lt;/p&gt;
&lt;p&gt;Does it sound intriguing? I hope it does, as we aim to learn and have fun.&lt;/p&gt;
&lt;h2 id=&quot;a-quick-recap-about-locking&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#a-quick-recap-about-locking&quot; aria-label=&quot;a quick recap about locking permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;A quick recap about locking&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Last week, we learned that locking is the simplest way to handle exclusive access to the resource.&lt;/strong&gt; Using it, the actor says, &lt;em&gt;“Until I finish, no other actor can access this resource!“&lt;/em&gt;. Others will need to wait or fail immediately. Who’s the actor? Think: user, service, node, processor, etc.&lt;/p&gt;
&lt;p&gt;Without locks, you can get unpredictable states - like a read model flipping from correct to incorrect or a file partially overwritten by multiple workers. Locks sacrifice a bit of parallelism and performance for the certainty that no two actors update the same resource simultaneously. In many cases, that’s the safest trade-off, especially if data correctness is a key factor.&lt;/p&gt;
&lt;p&gt;The basic flow looks like this:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fb28a3149c35ba02ce9102df4a9c61c4/7f529/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 96.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsTAAALEwEAmpwYAAACQ0lEQVR42pVU2XLbMBDz/39e+5Y6iQ85knXwvmRnimKp2o3taSZ5wHBEkdAC2NXqlCM+Yo4RVp1wbD0Oe41DY9BwPbYBejojh9vz91jdbxQSysWuK9huLH7+2OPlWaFrM4b+N7JPOJdvEAoSqyihkLzAW19XQSLZnL5Z4bkkeJ2gxwUt5ao+YiL0mL9LmOiRRXdIlOnx62la8GvCmnhrWOlXPczBITiD6A0CZYpX2TOkwKpiZtW2BvI+56riU8I5BUSSXSQv4J7NGI8Ow1GzugntYULfWagh/Dft1ZKsp+H25suSpJkSdpuhknVvBkfi0IzoW8M7mefCVwkT99h348h9X59PiVXLSnhj4JTCe3kM6YFwJpFVmU09otn2lOxrby4XAoyasd0GPDOkZkdvTan2fFphMJE+WbaKgZ3CtQqxIdDXvotsdIexz7yXbxr9gVDMntnEwfiKU5KG5uEUl3d8lvTNqGnBzDbKN610RxjpjYycwuZlwOZ1YCgTZcuEJKY943k94fVFcR3Zo3LGVhsuAa2EZE5CaNhjiZIcpWp64xnKskYXq+xgE/0Vj+Wd5ergdKgjea0w2Hg9NLLf9GhqpckVPsslUZCu/nlbYDmafac5CDMcQ4kfCdVQsN9pptZS6sTqSk16OJ6xXhvstqlekiT77oynJ00ojqLmSCrKl9/a6Z/k5OTLglClieF1FPnV6BZc2iZd9wKlOoZ0+/7q4T0uLfLx+bInq/yFmv30twe/8D/8DPJH7zuxSYKZH0bvD2VstaT5I3yhAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/fb28a3149c35ba02ce9102df4a9c61c4/507e6/image-2.webp 200w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/28a80/image-2.webp 400w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/8d2ea/image-2.webp 800w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/fb185/image-2.webp 825w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/fb28a3149c35ba02ce9102df4a9c61c4/36ca5/image-2.png 200w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/a3397/image-2.png 400w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/a331c/image-2.png 800w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/7f529/image-2.png 825w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/fb28a3149c35ba02ce9102df4a9c61c4/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;lock-definition&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#lock-definition&quot; aria-label=&quot;lock definition permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Lock definition&lt;/h2&gt;
&lt;p&gt;Let’s now define a high-level API for locking. I’ll use TypeScript again because I like it, and I have to choose some language.&lt;/p&gt;
&lt;p&gt;It can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Lock&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;acquire&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LockOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;tryAcquire&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LockOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LockOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  withAcquire&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Result&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LockOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Result&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LockOptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lockId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It has four methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;acquire&lt;/strong&gt; - either get access to the resource or wait if the lock is already held,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;tryAcquire&lt;/strong&gt; - either acquire lock or returns false if the lock is already held,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;release -&lt;/strong&gt; mark locked resource as available for others,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;withAcquire&lt;/strong&gt; - wrapper that will run a specific code wrapped with &lt;em&gt;acquire&lt;/em&gt; and &lt;em&gt;release&lt;/em&gt; method calls.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All methods take options with a specific lock identifier. It can represent the locked resource id or any other arbitrary text format.&lt;/p&gt;
&lt;p&gt;Of course, we could be more creative and add more options, such as timeouts, but let’s keep it focused.&lt;/p&gt;
&lt;h2 id=&quot;challenges-with-implementing-locking&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#challenges-with-implementing-locking&quot; aria-label=&quot;challenges with implementing locking permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Challenges with implementing locking&lt;/h2&gt;
&lt;p&gt;When implementing locking, we need to be sure we’ll acquire a specific lock for the single resource. That’s vulnerable to race conditions. We must also make it thread-safe, ensuring safety when multi-threads try to lock the same record.&lt;/p&gt;
&lt;p&gt;The last part is simpler in TypeScript, as it uses a &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop&quot;&gt;JavaScript event loop&lt;/a&gt; and is single-threaded. Node.js authors thought handling tasks sequentially (if done well) could be faster than handling and managing multiple threads. Per &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop&quot;&gt;MDN&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;A very interesting property of the event loop model is that JavaScript, unlike a lot of other languages, never blocks. Handling I/O is typically performed via events and callbacks, so when the application is waiting for an &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API&quot;&gt;IndexedDB&lt;/a&gt; query to return or a&lt;/em&gt; &lt;code class=&quot;language-text&quot;&gt;fetch()&lt;/code&gt; &lt;em&gt;request to return, it can still process other things like user input.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So if you’re doing a more classical approach like C#, Java, be aware that you’d need to use thread-safe constructions like &lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=net-9.0&quot;&gt;ConcurrentDictionary&lt;/a&gt;, &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html&quot;&gt;ConcurrentHashMap&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Luckily, in &lt;a href=&quot;/en/ordering-grouping-and-consistency/&quot;&gt;Ordering, Grouping and Consistency in Messaging systems&lt;/a&gt;, we implemented the Queue Broker capable of handling a single task per group of tasks. &lt;a href=&quot;/en/ordering-grouping-and-consistency/&quot;&gt;We used it to handle idempotency&lt;/a&gt;, which is a similar, but slightly different goal.&lt;/p&gt;
&lt;p&gt;To recap, our Queue Broker allows multiple tasks to be enqueued and handled asynchronously. It works similarly as &lt;a href=&quot;https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-fifo-queues.html&quot;&gt;Amazon SQS FIFO&lt;/a&gt; ensures task with the same task group ID are processed in the strict order.&lt;/p&gt;
&lt;p&gt;Groups are independent, so tasks for one group don’t block tasks in another. The other tasks from the same group will wait in the queue to be processed. Our Queue Broker uses a &lt;a href=&quot;https://www.architecture-weekly.com/i/147613456/single-writer-pattern&quot;&gt;single-writer pattern&lt;/a&gt; to ensure that scheduling happens sequentially. We can enqueue multiple tasks, but they’re not processed immediately; the background queue processing processes them. Thanks to that, we can control the concurrency.&lt;/p&gt;
&lt;p&gt;Let’s use it in our case!&lt;/p&gt;
&lt;h2 id=&quot;implementing-locks&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#implementing-locks&quot; aria-label=&quot;implementing locks permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Implementing Locks&lt;/h2&gt;
&lt;p&gt;Let’s use what we learn so far, starting with the Lock setup:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;InProcessLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Lock &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; taskProcessor &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueueBroker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    maxActiveTasks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Number&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;MAX_VALUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    maxQueueSize&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Number&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;MAX_VALUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Map to store ack functions of currently held locks: lockId -&gt; ack()&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; locks &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// (...) implementation will go here&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// acquire: (...),&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// tryAcquire: (...),&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// release: (...),&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// withAcquire: (...)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We defined the &lt;em&gt;InProcessLock&lt;/em&gt; function that will generate a new Lock. It has a queue broker without any limits on parallel processing. The only concurrency limit that we want to enforce is for the specific lock id.&lt;/p&gt;
&lt;p&gt;We’re also keeping the currently held locks in map cache.&lt;/p&gt;
&lt;h3 id=&quot;acquiring-lock&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#acquiring-lock&quot; aria-label=&quot;acquiring lock permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Acquiring Lock&lt;/h3&gt;
&lt;p&gt;Now, let’s implement the acquire function:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;acquire&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lockId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LockOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    taskProcessor
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ack &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;// Entering the locked scope.&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;// Cache the ack function for the lock id&lt;/span&gt;
          locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lockId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ack&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; taskGroupId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lockId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;If the lock is already held, we just queue up another task in the same group. QueueBroker ensures tasks in the same group run one at a time. We’re passing the lock id as the task group id.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When the task starts, it means the previous lock (if any) was released and now we have exclusive access.&lt;/p&gt;
&lt;p&gt;The queue broker completes task, when &lt;em&gt;ack&lt;/em&gt; callback is called. In &lt;em&gt;acquire&lt;/em&gt; we’re not calling it, as it should be called when we call &lt;em&gt;release&lt;/em&gt; method explicitly. To be able to do it, we cache the &lt;em&gt;ack&lt;/em&gt; callback in for the lock id in the previously defined locks map.&lt;/p&gt;
&lt;p&gt;Try acquire will look similarily, we can reuse the acquire function here:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tryAcquire&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lockId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LockOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// If lock is already held, fail immediately&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lockId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;acquire&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lockId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If the lock is already held we return &lt;em&gt;false&lt;/em&gt; immediately. Otherwise we call acquire method. If there’s a race condition and after doing the check other task also tries to access lock, then nothing wrong will happen. Just the first one will get the access, and the other will need to wait. Returning &lt;em&gt;false&lt;/em&gt; here is “best effort”.&lt;/p&gt;
&lt;h2 id=&quot;releasing-lock&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#releasing-lock&quot; aria-label=&quot;releasing lock permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Releasing Lock&lt;/h2&gt;
&lt;p&gt;If we’re calling the &lt;em&gt;acquire&lt;/em&gt; and &lt;em&gt;release&lt;/em&gt; functions explicitly, then we’re assuming that we’re know what we’re doing. &lt;strong&gt;That means, that release will be called after we finished the processing on the resource we wanted to lock.&lt;/strong&gt; Having that the release function can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lockId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LockOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; ack &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lockId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ack &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lockId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;ack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The logic is simple, we’re getting cached &lt;em&gt;ack&lt;/em&gt; handle for the specified lock. We’re calling this &lt;em&gt;ack&lt;/em&gt; informing queue broker that the task processing has finished. That means, that it’ll allow queue broker to take the next task for that lock id.&lt;/p&gt;
&lt;h2 id=&quot;locking-scope&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#locking-scope&quot; aria-label=&quot;locking scope permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Locking Scope&lt;/h2&gt;
&lt;p&gt;Let’s finish implementation with locking scope created in &lt;em&gt;withAcquire&lt;/em&gt; to easier handle locking scenarios.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;withAcquire&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Result&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lockId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; LockOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Result&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; taskProcessor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ack &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lockId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ack&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lockId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token function&quot;&gt;ack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; taskGroupId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; lockId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not much to explain, as you know all of that. Such syntax makes it just easier and safer to handle race conditions and not forget to release lock. It’ll be automatically released after running the handle callback.&lt;/p&gt;
&lt;p&gt;Example usage? Sure, for instance, if you’d like to ensure that only a single Test Container initialisatisation happens in parallel, this could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; container&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStoreDBContainer &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; startedContainer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StartedEventStoreDBContainer &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; startedCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; lock &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;InProcessLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getSharedEventStoreDBTestContainer&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  lock&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withAcquire&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;startedContainer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; startedContainer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;container&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        container &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;EventStoreDBContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;EVENTSTOREDB_DEFAULT_IMAGE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      startedContainer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      startedCount&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withLogConsumer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
        stream
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;data&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;err&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;line&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;end&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Stream closed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; startedContainer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; lockId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;SharedEventStoreDBTestContainer&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This function comes from &lt;a href=&quot;https://github.com/event-driven-io/emmett/blob/main/src/packages/emmett-testcontainers/src/eventStore/eventStoreDBContainer.ts&quot;&gt;Emmett Test Containers helpers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can also find the implementations for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/event-driven-io/emmett/blob/b5518834595e819443e0f8c6fd62ca41af12d89e/src/packages/emmett/src/taskProcessing/taskProcessor.ts&quot;&gt;QueueBroker (renamed to TaskProcessor)&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/event-driven-io/emmett/blob/b5518834595e819443e0f8c6fd62ca41af12d89e/src/packages/emmett/src/utils/locking/index.ts&quot;&gt;InProcessLock&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;cool-but-why-would-you-care&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cool-but-why-would-you-care&quot; aria-label=&quot;cool but why would you care permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cool, but why would you care?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;If you’re wondering, why would you try to implement queue or lock on your own&lt;/strong&gt; as you have such abstractions available in most of the dev environments then you’re on the right track: typically you should not!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, the goal of those articles is to show you, how understanding how the foundational patterns like queuing, single-writer, concurrency handling can help you in understanding tools you use. And how to compose them together.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There’s also an interesting observation to have you here. If you’re using queues with the ordering guarantee (mentioned AWS SQS FIFO, but also Kafka, etc.) then you can design the message passing in a way to steer concurrency of the processing.&lt;/p&gt;
&lt;p&gt;For instance, if you want to coordinate the processing of the specific distributed business processes, then you should ensure that they’re put into the same queue/partition.&lt;/p&gt;
&lt;p&gt;For instance, if you’re doing distributed processing like we did in:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4b8c3b976b8c7a4fc097aa6f067d4299/5b9f9/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACA0lEQVR42mWSTW/TQBCG/fcQ/4cDFwQiSFyCQEpiJeopObgEpKqiAi4cOCCq5sN2k1RRoCVtqsSx98O760hc+jK7bkMokR7Nanb29cw78RgTkFJBCOngXCLPc0hhoyrPsswppRxaa3fHGMemKBCdTvDyyVO82z+EpzQV0aWmwsIYaFNsH+6idSmklC5zdBb0IfvbDwK8D9p4UanAkzJ3xZI6WK7XkOnKPbId7ZJxAcEy6pzTJAK5YBDZ2gm2Ox0MwmO8flOFx2k0pWgsZTA+vwAbHyBXcjumjVobDH5eIA6/YBx/RfzjDBfHXeTRIW5IsNNp4+PeB1QePSfBW8/ukGJFnaQQXJSihHTecvIsLeEMPFuBpUvckGLQDbD38C0eP3h2NzL5Yk2nTmSWIOt3IS5jGBp9cJXivH8EI7nzzWJcNA5Dvq+SFZazBL+mc3h2y38FC8j1AulJAD75BMVTnCwkZt8PYNiSbLmt21mWncqKbn7TW7LKM7RVK+i2WGxIcImU/JHxEfTsG8LrHPP+ZxgaUW1r/8UKW1HGGDy7MeuPg84Ztb+eDZDNT8EvR5heZ1hMhxBp4u63tfewOkmSwAvDEFEUwUZHFCMandGfdewYxRHi0YTyOzX3GA6HTqPX68HzfR/NZtPRarVc9P3GlkbDh62p1+uo1Wr/YfP23lKtvsIflFkUJcruehMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/4b8c3b976b8c7a4fc097aa6f067d4299/507e6/image-3.webp 200w,
/static/4b8c3b976b8c7a4fc097aa6f067d4299/28a80/image-3.webp 400w,
/static/4b8c3b976b8c7a4fc097aa6f067d4299/8d2ea/image-3.webp 800w,
/static/4b8c3b976b8c7a4fc097aa6f067d4299/aa104/image-3.webp 1100w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/4b8c3b976b8c7a4fc097aa6f067d4299/36ca5/image-3.png 200w,
/static/4b8c3b976b8c7a4fc097aa6f067d4299/a3397/image-3.png 400w,
/static/4b8c3b976b8c7a4fc097aa6f067d4299/a331c/image-3.png 800w,
/static/4b8c3b976b8c7a4fc097aa6f067d4299/5b9f9/image-3.png 1100w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/4b8c3b976b8c7a4fc097aa6f067d4299/a331c/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;[&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 56px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC80lEQVR42o2VO0hbYRTHr4nvJ6VK1fiig7YIobo4ZDAWEgfpIg4+0aWDQ8DFki0FFReLLipOIopgfEDRRR0FBxdRR4cKVXAQI4nGmHu9/55zki9VWmM+OPnuvd/5fuf/Pc6JBsCiUbu8vHz38PDw1TAMbzQaHab+WzIjH9UPB4NBBzP8fr+Vey0UCtlp8BdizUTqzVT+JOa7phrB9uMOETI9HA7r3CezSCSinqOPj48GTyZhn7Wbm5v3BAyqiB6PBwMDA/KytbWFi4uL2IAZE06+0i8uLsLpdOLq6gpxMO7v739o1NfRPoT4w8bGhkmC0d3dLZOampqwvb39DKTregLIvkNDQzLMP7e3tzMaSa+j9d/xh/HxcQH29/fLpMHBQezvx3aDovM+4e5OXLGwsCBAl8uVUEhb9X9gb2/vM1VPm1I4Pz8vwLa2tpeBo6OjZnp6OmpqatDZ2SlgVsvPHR0d8tzX1ye93W5HdnY2WltbXwaOjIyYmZmZKC8vF2c2VsEB6uvrYbFYwONZWVkoLi5GQUEB3G53cmBGRgYqKytRXV2NoqIiTE5O4vT0FIeHh9jd3UVFRYUY++Tn578OZAUMy8nJQVdXF46Pj9HY2Ija2losLy9jenoavC1VVVWpA3mJvNTZ2Vl4vV6MjY3h6OgIDocDe3t7slRW+SqQD+Wpwp6eHlkqHwArXFpawtzcHHhblMJXD4UPgqOrg5mYmMDJyYko29nZQWlpKUpKSmCz2URp0ns4NTUl95CjM1Atq6GhAc3NzQIoKysTGB8K72V7e/s/wA90gSX1rq+vzZaWFlHGILbCwkJYrVakpaXJO0Pz8vKQm5srgQ8ODhKpR6wZjZLbRtUioIoDFQtsbm5iZWUFq6urYmtra1hfX0+8Kzs7O1MZpceBPlW+/PyBwDpVFSMeMSUz4vlJc8ME/ChAUvWGKs5P42/yplpkTS5rNO035fgXgfl8Pgv/DbAFAoFPNMAXy5WiuakKOc/Pz9+qv4A/x/GAJi+lEOYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/61627/image-4.webp 56w&quot;
              sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-4.png 56w&quot;
            sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-4.png&quot;
            alt=&quot;image 4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;Architecture Weekly&lt;/p&gt;
&lt;p&gt;Webinar #3 - Implementing Distributed Processes&lt;/p&gt;
&lt;p&gt;Distributed processes with an event-driven approach embrace the impossibility of the two-phase commit in distributed transactions. Instead of trying to make a big transaction across modules and databases, it does a sequence of microtransactions…&lt;/p&gt;
&lt;p&gt;Read more&lt;/p&gt;
&lt;p&gt;4 years ago · 6 likes · 4 comments · Oskar Dudycz&lt;/p&gt;
&lt;p&gt;](&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed?utm_source=substack&amp;#x26;utm_campaign=post_embed&amp;#x26;utm_medium=web&quot;&gt;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed?utm_source=substack&amp;#x26;utm_campaign=post_embed&amp;#x26;utm_medium=web&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;So Group Guest checkout coordinating a multiple single processing, then if we orchestrate it to have the messages for group checkout in a single queue/partition/stream then we’re getting the linearity of processing. Especially if we keep there both:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;commands&lt;/strong&gt; - informing on our intention to run a specific flow (e.g. performing a guest checkout).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;events&lt;/strong&gt; - informing on the stuff that happened (e.g. that guest checkout completed or failed).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If all of them will be processed sequentially, then we’re essentially locking the access for the particular process for a single handler.&lt;/p&gt;
&lt;p&gt;Of course, that has pros and cons. The more we put into the same queue, the lower throughput we get but stronger consistency. The less we put in the queue, the weeker consistency guarantees are.&lt;/p&gt;
&lt;p&gt;Still, with a proper modelling, and tradeoff analysis we can design our partitioning strategy to handle our scenario good enough. Some of that we already tackled in the webinar linked above.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you’d like me to expand on that in the next editions, please respond or leave a comment!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you don’t want, then please also do it, telling me on your preferences!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Distributed Locking: A Practical Guide]]></title><description><![CDATA[Was your data ever mysteriously overwritten? No? Think again. Have you noticed conflicting updates to the same data? Still nope? Lucky you…]]></description><link>https://event-driven.io/en/distributed-locking-a-practical-guide/</link><guid isPermaLink="false">https://event-driven.io/en/distributed-locking-a-practical-guide/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Was your data ever mysteriously overwritten? No? Think again. Have you noticed conflicting updates to the same data?&lt;/strong&gt; Still nope? Lucky you! I had cases when my data changed, and it looked like Marty was travelling back to the future. Once it was correct, then it was wrong, then correct again. Most of the time, twin services were writing to the sample place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In distributed systems, coordination is crucial.&lt;/strong&gt; Whenever you have multiple processes (or services) that could update or read the same data simultaneously, you risk data corruption, race conditions, or unwanted duplicates. The popular solution is to use distributed locks - a mechanism ensuring only one process can operate on a resource at a time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A distributed lock ensures that if one actor (node, service instance, etc.) changes a shared resource—like a database record, file, or external service—no other node can step in until the first node is finished.&lt;/strong&gt; While the concept is straightforward, implementing it across multiple machines requires careful design and a robust failure strategy.&lt;/p&gt;
&lt;p&gt;Today, we’ll try to discuss it and:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Explain&lt;/strong&gt; why distributed locking is needed in real-world scenarios.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Explore&lt;/strong&gt; how popular tools and systems implement locks (Redis, ZooKeeper, databases, Kubernetes single-instance setups, etc.).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Discuss&lt;/strong&gt; potential pitfalls—like deadlocks, lock expiration, and single points of failure—and how to address them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By the end, you should have a decent grasp of distributed locks, enough to make informed decisions about whether (and how) to use them in your architecture.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-why-distributed-locks-matter&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-why-distributed-locks-matter&quot; aria-label=&quot;1 why distributed locks matter permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. Why Distributed Locks Matter&lt;/h2&gt;
&lt;p&gt;When you scale an application to multiple machines or microservices, each one might update the same resource simultaneously. That can lead to writers overwriting each other. It’s the classic concurrency problem: &lt;strong&gt;who’s in charge of updating shared data?&lt;/strong&gt; Without a strategy to coordinate these updates, you risk inconsistent results.&lt;/p&gt;
&lt;p&gt;For example, you might have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multiple Writers are updating the same table row.&lt;/strong&gt; One process believes it has the latest data and changes, but another process overwrites it moments later.&lt;br&gt;
&lt;strong&gt;That’s a common issue when updating read models.&lt;/strong&gt; We need to ensure a single subscriber for a specific read model. When multiple microservices listen to an event stream but only one should update a secondary database, a lock ensures they don’t collide.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;At-Least-Once Messaging&lt;/strong&gt; leading to repeated deliveries of the same message, with multiple consumers each processing it as if it were unique.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A Global Resource&lt;/strong&gt; that can only support one writer at a time, such as a large file or an external API endpoint.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scheduled jobs:&lt;/strong&gt; If you schedule a job on multiple nodes to ensure reliability, you still want only the first node that grabs the lock to run it, preventing duplicates.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A &lt;strong&gt;distributed lock&lt;/strong&gt; is the simplest way to say, “Only one node can modify this resource until it’s finished.” Other nodes wait or fail immediately rather than attempting parallel writes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It can also be seen as&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Leader_election&quot;&gt;Leader Election Lite&lt;/a&gt;&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;:&lt;/strong&gt; If you only need “one node at a time” for a particular task, a plain lock is sometimes enough - there is no need for a full-blown leader election framework.&lt;/p&gt;
&lt;p&gt;Without locks, you can get unpredictable states - like a read model flipping from correct to incorrect or a file partially overwritten by multiple workers. Locks sacrifice a bit of parallelism for the certainty that no two nodes update the same resource simultaneously. In many cases, that’s the safest trade-off, especially if data correctness is paramount.&lt;/p&gt;
&lt;p&gt;There are other ways to handle concurrency (like &lt;a href=&quot;/en/idempotent_command_handling/&quot;&gt;idempotent actions&lt;/a&gt; or &lt;a href=&quot;/en/the-write-ahead-log-a-foundation/&quot;&gt;write-ahead logs&lt;/a&gt;). Still, a distributed lock is often the most direct solution when you truly need to avoid simultaneous writes.&lt;/p&gt;
&lt;h2 id=&quot;2-how-locks-typically-work&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-how-locks-typically-work&quot; aria-label=&quot;2 how locks typically work permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. How Locks Typically Work&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lock Request&lt;/strong&gt;&lt;br&gt;
A node determines it needs exclusive access to a shared resource and communicates with a lock manager (e.g., Redis, ZooKeeper, or a database) that supports TTL or ephemeral locks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Acquire Attempt&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If no lock currently exists, the node creates one, for instance by setting a Redis key with a time-to-live or by creating an ephemeral zNode in ZooKeeper.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If another node is holding the lock, this node either waits, fails immediately, or retries, depending on your chosen policy.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Critical Operation&lt;/strong&gt;&lt;br&gt;
After acquiring the lock, the node proceeds with the operation that must not be run in parallel—such as updating a database row, writing to a shared file, or sending a limited external API request.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Standard Release&lt;/strong&gt;&lt;br&gt;
Once finished, the node explicitly removes the lock record (e.g., deleting the Redis key or calling an unlock function). At this point, other contenders are free to acquire the lock.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Crash or Automatic Release&lt;/strong&gt;&lt;br&gt;
If the node fails or loses its session, the TTL expires, or the ephemeral mechanism detects the node’s absence. The lock manager then removes the lock, automatically merging into the same path as a standard release so a new node can acquire it. This avoids leaving the system stuck with a “zombie” lock that never gets freed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The basic flow would look like:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fb28a3149c35ba02ce9102df4a9c61c4/7f529/2024-12-23-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 96.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsTAAALEwEAmpwYAAACQ0lEQVR42pVU2XLbMBDz/39e+5Y6iQ85knXwvmRnimKp2o3taSZ5wHBEkdAC2NXqlCM+Yo4RVp1wbD0Oe41DY9BwPbYBejojh9vz91jdbxQSysWuK9huLH7+2OPlWaFrM4b+N7JPOJdvEAoSqyihkLzAW19XQSLZnL5Z4bkkeJ2gxwUt5ao+YiL0mL9LmOiRRXdIlOnx62la8GvCmnhrWOlXPczBITiD6A0CZYpX2TOkwKpiZtW2BvI+56riU8I5BUSSXSQv4J7NGI8Ow1GzugntYULfWagh/Dft1ZKsp+H25suSpJkSdpuhknVvBkfi0IzoW8M7mefCVwkT99h348h9X59PiVXLSnhj4JTCe3kM6YFwJpFVmU09otn2lOxrby4XAoyasd0GPDOkZkdvTan2fFphMJE+WbaKgZ3CtQqxIdDXvotsdIexz7yXbxr9gVDMntnEwfiKU5KG5uEUl3d8lvTNqGnBzDbKN610RxjpjYycwuZlwOZ1YCgTZcuEJKY943k94fVFcR3Zo3LGVhsuAa2EZE5CaNhjiZIcpWp64xnKskYXq+xgE/0Vj+Wd5ergdKgjea0w2Hg9NLLf9GhqpckVPsslUZCu/nlbYDmafac5CDMcQ4kfCdVQsN9pptZS6sTqSk16OJ6xXhvstqlekiT77oynJ00ojqLmSCrKl9/a6Z/k5OTLglClieF1FPnV6BZc2iZd9wKlOoZ0+/7q4T0uLfLx+bInq/yFmv30twe/8D/8DPJH7zuxSYKZH0bvD2VstaT5I3yhAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/fb28a3149c35ba02ce9102df4a9c61c4/507e6/2024-12-23-cover.webp 200w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/28a80/2024-12-23-cover.webp 400w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/8d2ea/2024-12-23-cover.webp 800w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/fb185/2024-12-23-cover.webp 825w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/fb28a3149c35ba02ce9102df4a9c61c4/36ca5/2024-12-23-cover.png 200w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/a3397/2024-12-23-cover.png 400w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/a331c/2024-12-23-cover.png 800w,
/static/fb28a3149c35ba02ce9102df4a9c61c4/7f529/2024-12-23-cover.png 825w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/fb28a3149c35ba02ce9102df4a9c61c4/a331c/2024-12-23-cover.png&quot;
            alt=&quot;2024 12 23 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The general lock acquisition workflow&lt;/p&gt;
&lt;p&gt;And the acquisition part with TTL handling:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 739px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/05cc8f908bb232448dc69ed8f1a13c02/7b99c/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 107.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC+klEQVR42o1V23baMBDM/39fc2kgBAgYG1uydbfspNNZOWlOm9DDwx5Z0mrYnZ1dbqYU8K/5PqBvE9raomsc9Dlw9dx7dHXkPmH0X9+J3Xw99HB9wtBn7PdnrB73uP3xhPu7Lfa7Dl2XYPoZydF3vAJwpjlGqLsMo2dGFXDY9aheDHSb0XcTIxyR/JWAYjkGjMEXm1NCMAbBWkwx8my5++7dRUBJx/TkqrU0h9NRoTn1jM7TLNyQCvDVHAaboLpIzlrsdmesVwfstjUOL4rWwQ7vHF4DOJMXPwSodizkO/OKuhoYaYLt38jrBHWW1ONlQAF5zR5vk3x7eKNJuinnOQpf8Q+vyTsCa0RnLwOKTHQ3ouMvV4cB9VEqGqm9iIEy+a6abtDfA0pEbZ3x/Bxwf9/i4aHDej3g9rYmdxbn01Si/iJ+01+OUDizOnKlaBUrrCTtVCIP5pPbxfx74XRZZf9x98khL15zJIeJ3Jhib9NIp1geiU+wofxIdLmYanryOZVvOQ92KdKNEN2rjOOLZzU9V4vD3lB7spdqT8jFZ0LTvJEOTWpqPD6SHq7rdVfOdfdaCnizOM943mg8rVpsnhS5a/HzocbTWlEicwHUrQACp9OM1arH3V1Dnk84HjOaM1jQeQH8aLU5J6aZy3fm2a95Ig0jwcJ7ypGCJq9mZIq5dI10jOfeDeNnyqU6RJaHc5LCDMVEezKiJDqRTTQO0Yof285FnKumrNkvoyw5/wHoi9YqcnfcW+yYulh1sDjRVDMWZ6MSOvb19rnCdlORjgP22wYbrnJuOe6ScwugOlOHG0Wnhg86miKX7OOtJndzAdQU/aBHmhTKE6hCK8OXPd/rjEFJpO8cBsNRxdBzGMu0ttpxL5GNTHPRmMzIQHnImTcytQ19I8IgPR7L/V+9vOhtEWl0Pclnv/qBANKzqThH2/OOHFuFMVoWTYoZ/qwX52GpfOlxzf8QQz51+T9pq7YMjSWIeP2A/RxlTImy6JqBHMm0GfE/fwH8DUCZmNLF2H4sAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/05cc8f908bb232448dc69ed8f1a13c02/507e6/image-2.webp 200w,
/static/05cc8f908bb232448dc69ed8f1a13c02/28a80/image-2.webp 400w,
/static/05cc8f908bb232448dc69ed8f1a13c02/f349b/image-2.webp 739w&quot;
              sizes=&quot;(max-width: 739px) 100vw, 739px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/05cc8f908bb232448dc69ed8f1a13c02/36ca5/image-2.png 200w,
/static/05cc8f908bb232448dc69ed8f1a13c02/a3397/image-2.png 400w,
/static/05cc8f908bb232448dc69ed8f1a13c02/7b99c/image-2.png 739w&quot;
            sizes=&quot;(max-width: 739px) 100vw, 739px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/05cc8f908bb232448dc69ed8f1a13c02/7b99c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;3-tools-for-distributed-locking&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-tools-for-distributed-locking&quot; aria-label=&quot;3 tools for distributed locking permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. Tools for Distributed Locking&lt;/h2&gt;
&lt;p&gt;There are many tools for distributed locking; let’s check the most popular for certain categories.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://redis.io/&quot;&gt;Redis&lt;/a&gt;&lt;/strong&gt; (and clones): You store a “lock key” in Redis with a time limit attached. If the lock key doesn’t exist, you create it and claim the lock. Once your task finishes, you remove the key. If your process crashes, Redis removes the key after the time limit expires, so no one stays locked out forever. This method is quick and works well if you already rely on Redis, but you should be cautious about network splits or cluster issues.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://zookeeper.apache.org/&quot;&gt;ZooKeeper&lt;/a&gt; / &lt;a href=&quot;https://etcd.io/&quot;&gt;etcd&lt;/a&gt;&lt;/strong&gt;: These tools consistently keep data across a group of servers. You write a small record saying, “I own this lock,” and if you go offline, the system notices and removes your record automatically. ZooKeeper and etcd are often used for more advanced cluster coordination or leader election, so they’re more complex to set up than Redis. However, they offer strong consistency guarantees.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Database Locks&lt;/strong&gt;: If you have a single database (like Postgres or MySQL) where all your data lives, you can let the database control concurrency. You request a named lock, and the database blocks any other request for that same lock name until you release it. It’s convenient if you already use a single DB for everything, but it may not scale well across multiple databases or regions, and frequent locking could cause contention.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Kubernetes Single-Instance&lt;/strong&gt;: Instead of coordinating multiple replicas, you literally run only one replica of your service. Because there’s only one service process, you avoid concurrency altogether at the application level. It’s the simplest approach - no lock code is needed. The downside is that you lose parallel processing and can’t spin up more replicas for high availability or scaling.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Distributed locks all share a common goal: ensure only one node does a particular thing at any given time. However, each tool mentioned approaches the problem with distinct designs, strengths, and failover behaviours.&lt;/p&gt;
&lt;p&gt;Let’s look at each tool’s big-picture purpose—why you’d even consider it—then move on to how it implements (or approximates) a lock. Lastly, let’s discuss a few technical details that matter once you start coding or troubleshooting.&lt;/p&gt;
&lt;h3 id=&quot;31-redis-in-memory-data-store-with-ttl-based-locking&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#31-redis-in-memory-data-store-with-ttl-based-locking&quot; aria-label=&quot;31 redis in memory data store with ttl based locking permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3.1 Redis (In-Memory Data Store with TTL-Based Locking)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Redis runs all commands in a single thread, so there is no risk of two commands interfering partway through.&lt;/strong&gt; It provides atomic commands to “claim” a key only if it doesn’t exist and supports a time-to-live (TTL). Redis allows multiple nodes to coordinate around a shared key space. Essentially, if one node sets a “lock key” successfully, that key signals to everyone else that the lock is taken.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Redis runs all commands in a single thread, so there is no risk of two commands interfering partway through.&lt;/strong&gt; If two clients try to create the same lock key simultaneously, Redis processes them in strict sequence: either the first wins or the second does, but never both.&lt;/p&gt;
&lt;p&gt;To create a lock, you need to create a “lock key” using a Redis command, for instance:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;SET lockKey node123 NX EX 30&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That looks cryptic, but let’s see what’s happening behind the scenes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Atomic Key Creation with NX.&lt;/strong&gt; The &lt;strong&gt;NX&lt;/strong&gt; option in the &lt;code class=&quot;language-text&quot;&gt;SET&lt;/code&gt; command means “set the key only if it does not already exist.” Redis checks the key’s existence and creates it if none is found. This is all done atomically, so if another client tries the same operation, one will succeed, and the other will fail.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Time-to-Live with EX.&lt;/strong&gt; The &lt;strong&gt;EX&lt;/strong&gt; option (short for “expire after X seconds”) tells Redis to remove the key automatically after the specified number of seconds. If the client (the lock holder) crashes, once the EX timer hits zero, Redis deletes the key. This prevents the lock from staying locked forever.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lock Ownership.&lt;/strong&gt; When a client sets the key (for example, &lt;code class=&quot;language-text&quot;&gt;SET lockKey node123 NX EX 30&lt;/code&gt;), if Redis replies with &lt;strong&gt;OK&lt;/strong&gt;, that client is considered to “own” the lock. If Redis replies with &lt;strong&gt;nil&lt;/strong&gt; or an empty response, it means another client already owns it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Renewals.&lt;/strong&gt; If you need more time, say your operation runs longer than the initial 30 seconds, you can extend the lock by calling something like &lt;code class=&quot;language-text&quot;&gt;PEXPIRE lockKey newTTL&lt;/code&gt;, which resets the timer. Alternatively, you could run another &lt;code class=&quot;language-text&quot;&gt;SET lockKey node123 NX EX 30&lt;/code&gt; if you do it carefully (although the recommended pattern is to check that you still own the lock before renewing).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Releasing the Lock.&lt;/strong&gt; Once finished, the client &lt;strong&gt;deletes&lt;/strong&gt; the key, typically with &lt;code class=&quot;language-text&quot;&gt;DEL lockKey&lt;/code&gt;. This frees the lock immediately so other nodes can acquire it. It’s crucial to verify that you are indeed the one who set the key (sometimes done by storing a unique value in the key, then checking it before deleting).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Handling Network Partitions.&lt;/strong&gt; In a simple single-node Redis, a partition can lead to confusion if the lock holder is cut off but still thinks it holds the lock. On the other hand, Redis might expire the key after the TTL, and another client might acquire the lock. To address these edge cases in larger clusters, you can use the &lt;a href=&quot;https://redis.io/docs/latest/develop/use/patterns/distributed-locks/&quot;&gt;Redlock algorithm&lt;/a&gt;, which coordinates across multiple Redis instances to reduce the chance of conflicting lock ownership during network splits.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach suffices for a &lt;strong&gt;“best effort”&lt;/strong&gt; distributed lock in most daily scenarios. However, if your environment is prone to frequent network partitions or requires extremely strict guarantees, more advanced configurations or tools might be necessary.&lt;/p&gt;
&lt;p&gt;Choosing Redis may be a good option if you already run a Redis cluster for caching or need a lightweight solution that’s easy to integrate.&lt;/p&gt;
&lt;p&gt;Performance is generally good, though absolute consistency under partitions can require more complex setups.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;32-zookeeper--etcd-strongly-consistent-key-value-stores-with-ephemeral-nodes&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#32-zookeeper--etcd-strongly-consistent-key-value-stores-with-ephemeral-nodes&quot; aria-label=&quot;32 zookeeper  etcd strongly consistent key value stores with ephemeral nodes permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3.2 ZooKeeper / etcd (Strongly Consistent Key-Value Stores with Ephemeral Nodes)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ZooKeeper and etcd each run as a cluster of nodes that keep data consistent across a majority of them (a quorum).&lt;/strong&gt; When you write a small record saying, “I own this lock,” that record is replicated so every node in the cluster knows about it. This consistency helps avoid confusion if one node is temporarily out of sync.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 480px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/77f3eec921ac14de2536ea05c16d5a49/a5e48/image-3.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQBA//EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAGjZ0dUof/EABkQAAIDAQAAAAAAAAAAAAAAAAACAwQUAf/aAAgBAQABBQLFAYIRKcDG+M7fQS8qc//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABwQAAICAgMAAAAAAAAAAAAAAAABAiEQIhFRcf/aAAgBAQAGPwLm8P3sesrKjIejP//EABsQAQEBAAIDAAAAAAAAAAAAAAERACFhcYHx/9oACAEBAAE/IbCOHvIFaHlwXSMbSeV2rv7hgSt53//aAAwDAQACAAMAAAAQRO//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAwEBPxCn/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAECAQE/EE1//8QAHxABAQACAQQDAAAAAAAAAAAAAREAITFBUWGhkbHR/9oACAEBAAE/EEkweBU9428gf0xK71j3OvONTRJE0/OOGlBIRifTLBPai1l95//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/77f3eec921ac14de2536ea05c16d5a49/507e6/image-3.webp 200w,
/static/77f3eec921ac14de2536ea05c16d5a49/28a80/image-3.webp 400w,
/static/77f3eec921ac14de2536ea05c16d5a49/cdb16/image-3.webp 480w&quot;
              sizes=&quot;(max-width: 480px) 100vw, 480px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/77f3eec921ac14de2536ea05c16d5a49/37402/image-3.jpg 200w,
/static/77f3eec921ac14de2536ea05c16d5a49/4cda9/image-3.jpg 400w,
/static/77f3eec921ac14de2536ea05c16d5a49/a5e48/image-3.jpg 480w&quot;
            sizes=&quot;(max-width: 480px) 100vw, 480px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/77f3eec921ac14de2536ea05c16d5a49/a5e48/image-3.jpg&quot;
            alt=&quot;Distributed Lock using Zookeeper&quot;
            title=&quot;Distributed Lock using Zookeeper&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;source: &lt;a href=&quot;https://youtube.com/watch?v=a-zatYN1Lx0&quot;&gt;https://youtube.com/watch?v=a-zatYN1Lx0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You create something like&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;/locks/lockKey&lt;/code&gt; &lt;strong&gt;in ZooKeeper or a key in etcd.&lt;/strong&gt; If there is no record, you can successfully claim the lock. If it already exists, you either wait or fail, depending on your locking recipe.&lt;/p&gt;
&lt;h4 id=&quot;releasing-lock&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#releasing-lock&quot; aria-label=&quot;releasing lock permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Releasing Lock&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;When you’re done, you delete the ephemeral node or lease key, signalling that source is available.&lt;/strong&gt; Since it’s ephemeral, the node is removed if you just disconnect cleanly. But if you do explicit cleanup, that’s more predictable.&lt;/p&gt;
&lt;p&gt;Other clients can “watch” that lock path. If the lock holder crashes, ZooKeeper or etcd detects the session loss and removes the node, instantly notifying watchers that the lock is free again. This allows any waiting client to move in and grab the lock right away.&lt;/p&gt;
&lt;p&gt;If a node is isolated from the quorum, ZooKeeper or etcd eventually considers that session dead and removes the ephemeral node. This auto-frees the lock. The newly updated cluster state reflects that the lock is available, so another node can pick it up.&lt;/p&gt;
&lt;h4 id=&quot;why-would-you-choose-zookeeper-or-etcd&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-would-you-choose-zookeeper-or-etcd&quot; aria-label=&quot;why would you choose zookeeper or etcd permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why would you Choose ZooKeeper or etcd?&lt;/h4&gt;
&lt;p&gt;If your environment already depends on them for cluster metadata or leader election, reusing them for locks is natural. They replicate data across multiple servers, so updates to the lock state are consistent, reducing the risk of split-brain scenarios. The ephemeral mechanism automatically frees locks if a session dies, so you don’t end up with “zombie” locks after crashes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;They offer stronger consistency guarantees at the cost of heavier operational overhead compared to Redis.&lt;/strong&gt; Setting up a ZooKeeper or etcd cluster is more complex than a single Redis node. They also come with higher operational overhead, so they’re often chosen by teams already using these tools for other cluster metadata.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;33-database-advisory-locks-using-your-existing-relational-db&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#33-database-advisory-locks-using-your-existing-relational-db&quot; aria-label=&quot;33 database advisory locks using your existing relational db permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3.3 Database Advisory Locks (Using Your Existing Relational DB)&lt;/h3&gt;
&lt;p&gt;Sometimes you’d rather avoid setting up additional infrastructure, like Redis or ZooKeeper, and simply rely on the single relational database you already use. Many SQL databases—PostgreSQL, MySQL, SQL Server, and others—provide built-in locking features that can help you coordinate concurrency directly in your existing environment. There are generally two ways to handle locks in a relational database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;row-level locks (like &lt;code class=&quot;language-text&quot;&gt;SELECT ... FOR UPDATE&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;advisory locks (like &lt;code class=&quot;language-text&quot;&gt;pg_advisory_lock&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;GET_LOCK&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They serve different needs, but both let you say, &lt;em&gt;“I want exclusive access to something”&lt;/em&gt; using your existing database.&lt;/p&gt;
&lt;p&gt;Below is &lt;strong&gt;Section 3.3&lt;/strong&gt; in a style &lt;strong&gt;matching&lt;/strong&gt; the Redis or ZooKeeper/etcd examples. It keeps the &lt;strong&gt;same structure&lt;/strong&gt;, including a &lt;strong&gt;“big picture”&lt;/strong&gt; intro, a &lt;strong&gt;step-by-step&lt;/strong&gt; explanation of how locks work, and a &lt;strong&gt;conclusion&lt;/strong&gt; about &lt;strong&gt;when&lt;/strong&gt; you’d choose this approach.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;33-database-locks-monolithic-or-single-db-setup&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#33-database-locks-monolithic-or-single-db-setup&quot; aria-label=&quot;33 database locks monolithic or single db setup permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3.3 Database Locks (Monolithic or Single-DB Setup)&lt;/h3&gt;
&lt;p&gt;Sometimes, you’d rather avoid setting up extra infrastructure like Redis or ZooKeeper. If your entire application relies on a single relational database (e.g., PostgreSQL, MySQL, SQL Server), you can often leverage &lt;strong&gt;built-in locking&lt;/strong&gt; features to manage concurrency. Many databases provide &lt;strong&gt;two&lt;/strong&gt; main methods:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Row-Level Locks&lt;/strong&gt; (e.g., &lt;code class=&quot;language-text&quot;&gt;SELECT ... FOR UPDATE&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Advisory Locks&lt;/strong&gt; (e.g., &lt;code class=&quot;language-text&quot;&gt;pg_advisory_lock&lt;/code&gt; in Postgres, &lt;code class=&quot;language-text&quot;&gt;GET_LOCK&lt;/code&gt; in MySQL, &lt;code class=&quot;language-text&quot;&gt;sp_getapplock&lt;/code&gt; in SQL Server)&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;They serve different needs, but both let you say, “I want exclusive access to something,” using your existing DB.&lt;/p&gt;
&lt;h4 id=&quot;row-level-locks-with-select--for-update&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#row-level-locks-with-select--for-update&quot; aria-label=&quot;row level locks with select  for update permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Row-Level Locks with &lt;code class=&quot;language-text&quot;&gt;SELECT ... FOR UPDATE&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;You can lock specific rows in a table by issuing something like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;BEGIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; locks &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; lock_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@loc_key&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* make changes */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;COMMIT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That looks cryptic, but here’s what’s happening.&lt;/p&gt;
&lt;p&gt;You wrap the SQL command with the transaction (BEGIN/COMMIT). The query includes &lt;code class=&quot;language-text&quot;&gt;FOR UPDATE&lt;/code&gt;, which tells the database: &lt;em&gt;“Lock this row so nobody else can modify it until I’m done.”&lt;/em&gt; If someone else tries to update the same row, they’ll have to wait.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The lock lasts until you commit or roll back the transaction.&lt;/strong&gt; At commit, the lock is released, letting other transactions proceed.&lt;/p&gt;
&lt;p&gt;You may also use regular rows instead of a dedicated locks table. It’s natural if your concurrency problem revolves around specific table rows. For others you need to define key that would represent the scope of locking&lt;/p&gt;
&lt;h4 id=&quot;advisory-locks-eg-pg_advisory_lock-or-get_lock&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#advisory-locks-eg-pg_advisory_lock-or-get_lock&quot; aria-label=&quot;advisory locks eg pg_advisory_lock or get_lock permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Advisory Locks (e.g., &lt;code class=&quot;language-text&quot;&gt;pg_advisory_lock&lt;/code&gt; or &lt;code class=&quot;language-text&quot;&gt;GET_LOCK&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;Instead of tying a lock to a specific row, you can lock an &lt;strong&gt;arbitrary&lt;/strong&gt; identifier:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- PostgreSQL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pg_advisory_lock&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* do something exclusive */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; pg_advisory_unlock&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12345&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;-- MySQL&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; GET_LOCK&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;readModel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;/* do something exclusive */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; RELEASE_LOCK&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;readModel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You pass an integer (&lt;code class=&quot;language-text&quot;&gt;12345&lt;/code&gt;) or string (&lt;code class=&quot;language-text&quot;&gt;&apos;readModel&apos;&lt;/code&gt;) to request a lock from the database’s internal lock manager. If it’s “free”, you acquire it; if it’s already taken, you wait or fail (depending on the timeout).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Locks are session-bound.&lt;/strong&gt; The lock belongs to your current DB session. The lock is freed automatically if that session ends due to a crash or normal exit. The downside is that the lock might remain locked if your session hangs around in a weird state.&lt;/p&gt;
&lt;p&gt;Those locks can be much lighter than row-level locks (e.g. &lt;a href=&quot;https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS&quot;&gt;PostgreSQL Advisory Locks&lt;/a&gt;). Yet, some of them may require passing integers, so you may need to map the string to a number, which can require some consistent hashing algorithm.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you want the reference implementations of PostgreSQL Advisory Locks, &lt;a href=&quot;https://github.com/event-driven-io/Pongo/blob/main/src/packages/dumbo/src/postgres/core/locks/advisoryLocks.ts&quot;&gt;check mine in Pongo&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;why-would-you-use-database-locks&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-would-you-use-database-locks&quot; aria-label=&quot;why would you use database locks permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why would you use database locks?&lt;/h4&gt;
&lt;p&gt;In many cases, database locks require minimal setup. There’s no need to spin up Redis or ZooKeeper if you already trust a single relational DB for everything. You can use familiar SQL, which can benefit many developers. You get transaction integrations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, they’re tight to a single database scope.&lt;/strong&gt; They don’t help if you have multiple databases or multi-region replication.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The other downside is lock contention.&lt;/strong&gt; A busy system constantly grabbing locks could block queries and reduce performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You also can get deadlocks&lt;/strong&gt; if multiple transactions lock rows or advisory locks in different orders. Monitoring is crucial.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;34-kubernetes-single-instance-deployments-an-operational-constraint&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#34-kubernetes-single-instance-deployments-an-operational-constraint&quot; aria-label=&quot;34 kubernetes single instance deployments an operational constraint permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3.4 Kubernetes Single-Instance Deployments (An Operational Constraint)&lt;/h3&gt;
&lt;p&gt;Sometimes, you don’t need a distributed lock at all—you just need to ensure there’s no possibility of concurrency. In Kubernetes, you can tell the cluster to run exactly one replica of your service. With only one pod, you don’t risk two pods writing to the same resource simultaneously. This approach is straightforward but also very limiting.&lt;/p&gt;
&lt;h4 id=&quot;how-it-works-in-terms-of-locking&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-it-works-in-terms-of-locking&quot; aria-label=&quot;how it works in terms of locking permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;How It Works in Terms of Locking&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Actual Lock&lt;/strong&gt;: Technically, you don’t lock anything. You simply rely on the fact that setting &lt;code class=&quot;language-text&quot;&gt;replicas: 1&lt;/code&gt; in your Deployment or &lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/&quot;&gt;StatefulSet&lt;/a&gt; ensures the scheduler never spawns more instances. You specify exactly one replica for your service. Kubernetes never spawns a second one, so concurrency can’t occur at the application level.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;One Pod, One Writer&lt;/strong&gt;: If that single pod is alive, it’s the only one doing the work. If it crashes, Kubernetes restarts it, but you might have downtime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Concurrency&lt;/strong&gt;: Because there’s literally just one instance, you don’t need to coordinate or lock anything. You’re effectively sidestepping the whole locking problem&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Parallelism or scaling&lt;/strong&gt;: If you eventually need multiple pods, you’ll have to switch to an actual lock approach. If traffic spikes or you want high availability, you can’t spin up more pods without moving to a distributed locking mechanism.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Funnily enough, Kubernetes stores all cluster data in &lt;a href=&quot;https://etcd.io/&quot;&gt;etcd&lt;/a&gt;.&lt;/strong&gt; &lt;a href=&quot;https://etcd.io/&quot;&gt;etcd&lt;/a&gt; keeps the entire cluster’s state: Deployments, Services, ConfigMaps, and so on. However, the cluster doesn’t perform ephemeral lock logic for a single-instance Deployment. It merely stores “replicas: 1” and lets the controller keep the Pod count in sync with that number.&lt;/p&gt;
&lt;p&gt;Kubernetes doesn’t use the &lt;em&gt;lock record&lt;/em&gt; as described before with Zookeeper/etcd section. The Deployment/Stateful Set controller periodically checks how many Pods are currently running for that Deployment. If there are zero (e.g., the Pod crashed), the controller starts a new Pod to reach the desired count of one. If there’s already one Pod, the controller does nothing—it matches the desired state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In Kubernetes, the mechanisms and controllers designed to manage the number of pods are quite robust, but in highly dynamic or unusual situations, there might be brief moments where conditions could lead to more than one pod being created temporarily.&lt;/strong&gt; However, such occurrences are generally rare and usually quickly corrected by Kubernetes’ control loops. For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rapid Scale-Up and Scale-Down:&lt;/strong&gt; If you rapidly change the replicas count up and then back down (or vice versa), it might lead to the Kubernetes scheduler and the replica set controller temporarily creating more pods than desired. This can occur in highly dynamic environments where scripts or operators frequently adjust the replica counts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Controller Delays:&lt;/strong&gt; Kubernetes controllers periodically reconcile the desired state with the actual state. If there’s a delay or lag in the controller’s loop (due to cluster load or other disruptions), you might briefly see more pods than expected.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Network Partitions or etcd Availability Issues:&lt;/strong&gt; If parts of the cluster cannot communicate with each other or with etcd (where the desired state is stored), different nodes might make different decisions. For example, one node might not know that a new pod has already been scheduled elsewhere and might schedule another.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why You’d Choose Single-Instance in Kubernetes?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you just need to enforce that there’s a single instance processing background job (read model handling, job processing) and you have Kubernetes set up, then it’s a decent choice. You remove the concurrency at all. You don’t spin up Redis or ZooKeeper just to handle locks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, the race conditions can be dangerous for high traffic or important cases. It’s not fully reliable.&lt;/strong&gt; So, it’s a decent choice for &lt;strong&gt;rarely Triggered Tasks&lt;/strong&gt;: If you have a once-a-day job and don’t care about parallelism or availability, one pod might be enough.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;35-tldr-on-locking-tooling-choice&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#35-tldr-on-locking-tooling-choice&quot; aria-label=&quot;35 tldr on locking tooling choice permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3.5 TLDR on locking tooling choice&lt;/h3&gt;
&lt;p&gt;Here’s my recommended decision-making scheme for locking mechanism:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If you have a single relational DB handling all app state, advisory locks might suffice&lt;/strong&gt; (and require no extra moving parts and minimal infrastructure). Many ready-made packages provide that. Inside modules locks or monolithic apps using a single DB instance are good use cases here. Beware: database locks are harder to scale across multiple DB nodes. They can become a contention point if locks are frequent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If your environment already includes a Redis cluster for caching and you want a simpler ephemeral lock, Redis is a natural fit.&lt;/strong&gt; If you want an application-level lock across multiple services, Redis is a decent candidate. Remember that it can bring a potential edge case under network partitions; multi-node setups may require Redlock for stronger safety.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If concurrency is never desired or is completely out of scope for a specific microservice, AND you’re using already Kubernetes a single-instance Kubernetes approach may be acceptable.&lt;/strong&gt; However, it clearly sacrifices high availability and scaling. It’s not really a distributed lock at all, just an operational limit of “one pod.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If your microservices need advanced coordination (like leader election, watchers, or strongly consistent state), consider ZooKeeper or etcd.&lt;/strong&gt; It’s the most configurable option and the most scalable. Yet it’s heavier to operate than a simple single Redis or DB solution. It’s also another service to operate. And pretty complex one.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-potential-pitfalls-and-best-practices&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#4-potential-pitfalls-and-best-practices&quot; aria-label=&quot;4 potential pitfalls and best practices permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;4. Potential Pitfalls and Best Practices&lt;/h2&gt;
&lt;h3 id=&quot;41-deadlocks-and-lock-ordering&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#41-deadlocks-and-lock-ordering&quot; aria-label=&quot;41 deadlocks and lock ordering permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;4.1 Deadlocks and Lock Ordering&lt;/h3&gt;
&lt;p&gt;If your system needs to acquire multiple locks at once, you risk deadlocks (e.g., process A has Lock1 and wants Lock2; process B has Lock2 and wants Lock1). The best practice is to lock in a consistent order globally or use carefully designed transaction boundaries. I’ll show you next week how you could use queuing and single writer for that.&lt;/p&gt;
&lt;h3 id=&quot;42-lock-contention&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#42-lock-contention&quot; aria-label=&quot;42 lock contention permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;4.2 Lock Contention&lt;/h3&gt;
&lt;p&gt;Locks serialize access. If too many services fight for the same lock, your system effectively becomes single-threaded. To avoid bottlenecks, lock only the smallest critical sections. If concurrency at some granularity is acceptable, consider sharding or partitioned locks.&lt;/p&gt;
&lt;h3 id=&quot;43-crash-handling-ttl-ephemeral-sessions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#43-crash-handling-ttl-ephemeral-sessions&quot; aria-label=&quot;43 crash handling ttl ephemeral sessions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;4.3 Crash Handling (TTL, Ephemeral Sessions)&lt;/h3&gt;
&lt;p&gt;What if the lock holder dies mid-operation? This is the classic reason for &lt;strong&gt;TTL&lt;/strong&gt; or ephemeral sessions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TTL&lt;/strong&gt;: If the process doesn’t extend the TTL, the lock is auto-freed. Another node can proceed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ephemeral&lt;/strong&gt;: The ephemeral node is removed if the session disappears (like in ZooKeeper).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without these, you risk &lt;strong&gt;permanent blocking&lt;/strong&gt; if someone forgets to release or if the holder abruptly goes offline.&lt;/p&gt;
&lt;h3 id=&quot;44-single-point-of-failure&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#44-single-point-of-failure&quot; aria-label=&quot;44 single point of failure permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;4.4 Single Point of Failure&lt;/h3&gt;
&lt;p&gt;If you rely on a single Redis instance or a single ZooKeeper node, your lock manager can fail. Always consider using a clustered or highly available setup, such as Redis with sentinel or cluster mode or a ZooKeeper ensemble of three or five nodes.&lt;/p&gt;
&lt;h3 id=&quot;45-clock-skew-and-network-partitions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#45-clock-skew-and-network-partitions&quot; aria-label=&quot;45 clock skew and network partitions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;4.5 Clock Skew and Network Partitions&lt;/h3&gt;
&lt;p&gt;Systems like Redlock try to handle partial failures, but no distributed lock can be 100% guaranteed if your network is severely partitioned (CAP theorem territory). You might end up with multiple holders, each believing they’re the only one. Proper design, timeouts, and conflict detection help reduce these edge cases.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Distributed locks are a fundamental tool for &lt;strong&gt;coordinating concurrency&lt;/strong&gt; across systems. Whether you choose Redis (with TTL-based locks), ZooKeeper (ephemeral nodes), database locks, or even just run a single replica in Kubernetes, the core principle remains: only one party can operate on a resource at a time. Handling &lt;strong&gt;failures&lt;/strong&gt; (crashed holders) is crucial, which is why ephemeral or time-based mechanisms are standard. Each approach has trade-offs in complexity, performance, and fault-tolerance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt; for senior engineers and architects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Try to avoid distributed locking if you can.&lt;/strong&gt; Usually, they’re a good sanity checkpoint to validate why you got here and if you really need it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use locks carefully&lt;/strong&gt;: Over-locking can degrade concurrency, but it’s critical when data corruption or double-processing must be prevented.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Choose the right store&lt;/strong&gt;: Redis is often the simplest if you already run it. ZooKeeper suits advanced coordination. DB locks can be fine for single-region or monolithic architectures.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Plan for failure&lt;/strong&gt;: Implement TTL/ephemeral locks to avoid “zombie” or stuck locks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Zero concurrency?&lt;/strong&gt;: Setting &lt;code class=&quot;language-text&quot;&gt;replicas: 1&lt;/code&gt; in a StatefulSet or Deployment, concurrency is removed but multi-replica scaling is forfeited. It’s not a distributed lock—just an operational constraint.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Understand trade-offs&lt;/strong&gt;: If your scenario is straightforward, a lock-based single subscription might be simpler than full leader election. For advanced cluster coordination, leader election or a robust distributed approach might be more robust.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Distributed systems will always have complexities, but a well-implemented distributed lock (or a strategic single-instance approach) can tame the chaos of concurrency—keeping your data consistent and your architecture stable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are your experiences, use cases, and challenges with distributed locking?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And hey, all the best for Christmas if you celebrate it. If you don’t, try to also get the chance to rest a bit. And if you don’t want to, that’s fine, as long as you’re happy!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[On getting the meaningful discussions, and why that's important]]></title><description><![CDATA[Welcome to the next week! This time, I’d like to go less techy and focus more on the soft part of being an architect. As much as I like to…]]></description><link>https://event-driven.io/en/on-getting-the-meaningful-discussions/</link><guid isPermaLink="false">https://event-driven.io/en/on-getting-the-meaningful-discussions/</guid><content:encoded>&lt;p&gt;Welcome to the next week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This time, I’d like to go less techy and focus more on the soft part of being an architect.&lt;/strong&gt; As much as I like to show code and explain the application of the technologies and patterns, I don’t want to make this newsletter Yet Another System Design Interview Preparation. I think it’s important to know patterns and technologies, but I also think it is more important to apply them in practice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To put our design into practice, we need to be able to persuade our colleagues, stakeholders, and other peers.&lt;/strong&gt; Without the ability to explain and persuade, even the best design will not be applied. And we’re doing ourselves harm if we spend hours, days, or even weeks and then don’t make additional effort to &lt;em&gt;sell&lt;/em&gt; our idea.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So yes, being an architect is also about talking.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Some may even cynically say that it’s only about talking.&lt;/p&gt;
&lt;p&gt;I disagree with that. I think Architects should be hands-on, and I even wrote a (not-so?) humble &lt;a href=&quot;/en/architect_manifesto/&quot;&gt;Architect Manifesto&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yet, let’s focus today on how to strike the right balance between talking and not talking and on how to talk to achieve the outcome.&lt;/strong&gt; Let me show you a few rules that work for me. I hope they can also help you. Let’s start with points and then follow up on each of them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ask and listen more often than tell.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It’s okay to agree to disagree.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Split the discussions on facts from discussions on feelings.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Focus on similarities, not differences.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Avoid jargon at all costs.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ad51bab7a699dfe78b72ca9aeb4b5084/c60e9/2024-12-16-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQCAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAP/aAAwDAQACEAMQAAABZitSLhlkf//EABsQAAICAwEAAAAAAAAAAAAAAAECACEDBBES/9oACAEBAAEFArmY0NzyGchwOtP/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAeEAACAQMFAAAAAAAAAAAAAAAAEQECEBIhMVJhgf/aAAgBAQAGPwLbQjjIsH6JlXVv/8QAGxABAAMBAAMAAAAAAAAAAAAAAQARIUExUYH/2gAIAQEAAT8hEN7fTzBd/YRrufYLnG19gWLwsZ//2gAMAwEAAgADAAAAEEPv/8QAFREBAQAAAAAAAAAAAAAAAAAAEDH/2gAIAQMBAT8Qh//EABcRAAMBAAAAAAAAAAAAAAAAAAEQETH/2gAIAQIBAT8Q0Vf/xAAcEAEBAQEAAgMAAAAAAAAAAAABESEAMWFBobH/2gAIAQEAAT8QluLME385JKKAzZih8nGRLMxfrkgRHoLx7lzgawJHWYcxSnf/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/ad51bab7a699dfe78b72ca9aeb4b5084/507e6/2024-12-16-cover.webp 200w,
/static/ad51bab7a699dfe78b72ca9aeb4b5084/28a80/2024-12-16-cover.webp 400w,
/static/ad51bab7a699dfe78b72ca9aeb4b5084/8d2ea/2024-12-16-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/ad51bab7a699dfe78b72ca9aeb4b5084/37402/2024-12-16-cover.jpg 200w,
/static/ad51bab7a699dfe78b72ca9aeb4b5084/4cda9/2024-12-16-cover.jpg 400w,
/static/ad51bab7a699dfe78b72ca9aeb4b5084/c60e9/2024-12-16-cover.jpg 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/ad51bab7a699dfe78b72ca9aeb4b5084/c60e9/2024-12-16-cover.jpg&quot;
            alt=&quot;Why Did The Office End? - Cast &amp;amp; Crew Comment&quot;
            title=&quot;Why Did The Office End? - Cast &amp;amp; Crew Comment&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://couchpotatoshop.com/blogs/the-office-news/why-did-the-office-end&quot;&gt;https://couchpotatoshop.com/blogs/the-office-news/why-did-the-office-end&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;1-ask-and-listen-more-often-than-tell&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-ask-and-listen-more-often-than-tell&quot; aria-label=&quot;1 ask and listen more often than tell permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. Ask and listen more often than tell&lt;/h2&gt;
&lt;p&gt;Too often, we’re not listening; we’re just waiting for the prepared statement that’s in the back of our heads. Whatever other people say doesn’t matter. We’re just waiting because we think we’re polite and &lt;em&gt;giving others a space to speak&lt;/em&gt;. But actually, if we could, we’d just speak our idea. We could even wear earplugs, as we only adjust how we connect our phrase to what others just spoke. Not the idea; it’s great.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m not sure how about you, but I don’t always have the best ideas in the room.&lt;/strong&gt; Unfortunate but true. Still, I have prepared statements about my &lt;em&gt;great&lt;/em&gt; ideas. Even more unfortunate and even more true.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jokes aside, listening to others and asking for clarification is a must.&lt;/strong&gt; It’s our due diligence. Even if we’re the smartest person in the room, we could overlook some details and miss the important perspective.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Having the last word is not only a sign of power.&lt;/strong&gt; It’s also a smart move. If we spoke first and gave the solution, we wouldn’t learn about potentially better solutions. If our (not-so) great idea failed, then we’d be last to blame. If a cynical argument speaks to you, then we can always say &lt;em&gt;“that’s also what was our idea”,&lt;/em&gt; to the best thing we have heard so far.&lt;/p&gt;
&lt;p&gt;Also, last, but not least, it’s about transparency, building camaraderie, and a safe space for discussion. In the longer term, that’ll pay off.&lt;/p&gt;
&lt;p&gt;Won’t that lead to &lt;em&gt;analysis-paralysis&lt;/em&gt;? That can happen, but…&lt;/p&gt;
&lt;h2 id=&quot;2-its-okay-to-agree-to-disagree&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-its-okay-to-agree-to-disagree&quot; aria-label=&quot;2 its okay to agree to disagree permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. It’s okay to agree to disagree&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I had one IT Department Director who always tried to persuade us.&lt;/strong&gt; He always wanted our agreement on his idea. He didn’t stop even if we said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“We don’t think that’s optimal solution, but we can do it this way.”&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sometimes, a not-optimal decision is better than no decision, so he could say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ok, then do it and we’ll evaluate the outcome.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Still, he wanted to persuade us. Initially, we spent hours discussing his solutions, knowing that we would eventually go his way. His way or highway was the rule. But, after discussions. Then we became sneaky, and nodded much quicker, just not to waste time. But that wasn’t as sneaky as we thought. When we did it, and that didn’t work (surprise!), he was saying:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Why didn’t you challenge my ideas?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Sometimes, there’s no best way. And we need to embrace that.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We need to choose something. And we shouldn’t be doing that blindly, but still make the choice. I wrote about it longer &lt;a href=&quot;/en/why_are_we_afraid_of_our_decisions/&quot;&gt;Why are we afraid of our decisions?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We need to note the alternatives and write explicitly why we selected a specific way. And suggest when to reevaluate our decision.&lt;/p&gt;
&lt;p&gt;If we’re leaders, then it’s great if we give our people the possibility to discuss that, but if we’re not allowing them to win, then it’s actually not a battle but a rigged fight. Yeah, I know fight analogies in programming don’t suit you well, but I hope you get the gist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s better to explicitly state that we disagree and explain why.&lt;/strong&gt; We don’t need to agree as long as we can come up with an acceptable solution for all of us.&lt;/p&gt;
&lt;p&gt;We should prefer consensus over compromise, but that’s not always possible. We’re also hired to make hard decisions. &lt;a href=&quot;https://www.youtube.com/watch?v=M0SjU95U3-k&quot;&gt;Somebody’s Gotta Do It!&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;3-split-the-discussions-on-facts-from-discussions-on-feelings&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-split-the-discussions-on-facts-from-discussions-on-feelings&quot; aria-label=&quot;3 split the discussions on facts from discussions on feelings permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. Split the discussions on facts from discussions on feelings&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Mixing feelings and facts leads nowhere. Or actually, it leads somewhere: to endless, heated discussions.&lt;/strong&gt; You saw that on socials, right? You were even part of it, aye? How to avoid it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unsurprisingly, we need to prepare a bit&lt;/strong&gt;—a bit can be a whole a lottta work. We should start by breaking down our problem and finding its essence. One possible way is to Follow the Money. We need to know WHY we’re doing it and WHAT the biggest challenge is. &lt;strong&gt;That’s hard, but just the starting point.&lt;/strong&gt; Then we can start to discuss HOW.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Knowing facts, we can talk about specific solutions.&lt;/strong&gt; It’s easier to avoid the impressions sphere. Facts are facts; we can’t dispute them but can dispute the interpretations. And usually, we will, but we should keep such discussions on a leash.&lt;/p&gt;
&lt;p&gt;Embracing that, we should give someone room for subjective evaluation. &lt;strong&gt;So again, ask and listen more often than tell.&lt;/strong&gt; Then, you can always agree to disagree.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I always try not to assume that I need to convince someone.&lt;/strong&gt; I’m more focused on presenting and explaining my ideas rather than persuading. That helps to focus first on facts. If I present the current state and my ideas in a way as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Listen, my understanding is that this works this and that.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;In my opinion, if we choose X over Y, then we’ll get the advantage Z because of ABC.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And yes, that’s weird, as I just presented facts as personal opinion or understanding. But this helps to highlight, that we already embraced that our interpretation of facts can be skewed.&lt;/p&gt;
&lt;p&gt;Those are &lt;em&gt;our facts&lt;/em&gt;. As long as we don’t go &lt;em&gt;my facts are the myty&lt;/em&gt;, then we’re good.&lt;/p&gt;
&lt;p&gt;Now, we’re leaving the space for personal evaluation for others. They can judge if our biases didn’t impact the presentation and think about whether they like it or not.&lt;/p&gt;
&lt;p&gt;If they agree, fine; if not, we can discuss it. It’s easier to discuss why someone doesn’t like it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If we mix these two levels, it’s hard to reach an agreement.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Besides, you already know that someone when someone agrees with us doesn’t mean we’ve convinced them. They could agree with us because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;they wave their hand and don’t care,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;they want us to leave them alone,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;they objectively see that someone is right, but it doesn’t matter because they subjectively don’t like it anyway.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m sure that you had cases where you thought:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Well, yes, it may be a bike with the best parameters, but I won’t buy it because it’s ugly and if I had to look at it every day I would puke.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&quot;4-focus-on-similarities-not-differences&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#4-focus-on-similarities-not-differences&quot; aria-label=&quot;4 focus on similarities not differences permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;4. Focus on similarities, not differences&lt;/h2&gt;
&lt;p&gt;That’s also related to using adjectives such as &lt;a href=&quot;https://www.youtube.com/watch?v=gAjR4_CbPpQ&quot;&gt;better, faster, stronger&lt;/a&gt;. When we come up with a much different proposal, we immediately put people in a defensive position. Their first thought is that we don’t like their solution. That’s probably true, but will it help if we highlight that?&lt;/p&gt;
&lt;p&gt;Especially if we start to say that our solution is &lt;strong&gt;simpler, less complicated, and easier to maintain; all those are meaningless adjectives.&lt;/strong&gt; They all mean different things to each person. We usually use them to hand-waist other people’s arguments.&lt;/p&gt;
&lt;p&gt;And you know what happens if we put other people into defensive positions? They will strike back, and now we must defend our position. And doing that one against the other is usually a lost battle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why we should focus on working together instead of fighting and battling.&lt;/strong&gt; When explaining your solution and letting others speak, try to show points in which your solution matches other people’s principles. You don’t need to lie; your solution is probably not as revolutionary as you may perceive it. It’s probably similar in many ways to others, just the pin points are located in different places. For differences, focus on facts and discuss others’ feelings on them.&lt;/p&gt;
&lt;p&gt;Important note is please, please, don’t tell phrases like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I like your solution, &lt;strong&gt;BUT&lt;/strong&gt;…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My solution is similar to yours, &lt;strong&gt;BUT…&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Do you know that BUT has a second name? It does; they call him Invalidator. It invalidates any kind words used before it in the sentence.&lt;/p&gt;
&lt;p&gt;If you do it well, then you can enhance your solution by including other people, and you’ll again build camaraderie.&lt;/p&gt;
&lt;h2 id=&quot;5-avoid-jargon-at-all-costs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#5-avoid-jargon-at-all-costs&quot; aria-label=&quot;5 avoid jargon at all costs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;5. Avoid jargon at all costs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I’m trying to avoid using jargon and acronyms or assuming someone should know something.&lt;/strong&gt; Using terms like aggregate, bounded context, subdomain, value stream doesn’t get us closer to mutual understanding.&lt;/p&gt;
&lt;p&gt;Better be precise and explain your assumptions using simple words. Even if you’re talking to technical people, you may be surprised that they may not know or understand terms differently.&lt;/p&gt;
&lt;p&gt;Instead of discussing the real issue, we can start arguing on terms that don’t matter. Then you may get annoyed, even if you made a false assumption.&lt;/p&gt;
&lt;p&gt;When explaining, start by explaining what issue you want to solve. Then, after discussing potential solutions, you can name the pattern, like&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Hey, by the way, this is called aggregate.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not the other way around.&lt;/p&gt;
&lt;p&gt;People that don’t know jargon will be lost when you use it. And those that know those terms, probably don’t have to hear those terms, they will get it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I’m sure that there’s more advice to that, but you can start from that. Please drop me a note if that was helpful or if you want me to come up with soft skills content here from time to time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please also share your piece of advice with me!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Check also more in my related blogs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/a_few_words_on_communication/&quot;&gt;A few words on communication&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/bring_me_problems_not_solutions/&quot;&gt;Bring me problems, not solutions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/fifteen_tips_on_how_to_run_meetings_effectively/&quot;&gt;15 tips on how to run meetings effectively&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/follow_the_money/&quot;&gt;Follow the money to get a better design&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how_to_design_software_architecture_pragmatically/&quot;&gt;How to design software architecture pragmatically&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[The Write-Ahead Log: A Foundation for Reliability in Databases and Distributed systems]]></title><description><![CDATA[I want to talk with you today about the Write-Ahead Log concept. Why? Last month, I gave the “PostgreSQL Superpowers” talk again twice at…]]></description><link>https://event-driven.io/en/the-write-ahead-log-a-foundation/</link><guid isPermaLink="false">https://event-driven.io/en/the-write-ahead-log-a-foundation/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;I want to talk with you today about the Write-Ahead Log concept.&lt;/strong&gt; Why?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Last month, I gave the “PostgreSQL Superpowers” talk again twice at .NET meetups.&lt;/strong&gt; And I had random thoughts on databases and .NET developers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;most of them don’t do SQL, just EF,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;don’t use Explain to analyse query plans,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MSSQL is still the go-to,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PostgreSQL is still seen only as a no-cost alternative,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DB is just a bag for data for them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They don’t use specific features an exact db gives them (but still choose MSSQL),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;most haven’t seen stored procedures and while loop in SQL for some time (if at all),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rarely use views and even rarer materialised views,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;don’t remember what Write-Ahead Log and Transaction Log are.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of those are inherently related to the Microsoft community (preferences for MS tooling), but the rest align with the general state of the art. Of course, those observations are anecdotal evidence, but I noticed that in multiple places and countries, so I think that they’re close to the point.&lt;/p&gt;
&lt;p&gt;The observations were a bit disappointing, but I don’t want to complain; I want to be proactive and help you and your colleagues improve. Step by step.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Today, let’s take the first step to close the knowledge of one of the most underrated concepts in distributed systems and databases: Write-Ahead Log.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-challenge-reliability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-challenge-reliability&quot; aria-label=&quot;the challenge reliability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Challenge: Reliability&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Distributed systems, databases, and message brokers must manage the state reliably. The challenge remains whether you’re dealing with PostgreSQL, Kafka, or MongoDB.&lt;/strong&gt; You must ensure a system can recover from crashes, maintain durability, and replicate changes efficiently without compromising performance. State management in such systems is filled with complexities—updates can fail midway, nodes can crash unexpectedly, and networks can introduce delays or partitioning. In a nutshell: &lt;a href=&quot;https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing&quot;&gt;Fallacies of distributed computing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To tackle these challenges, most reliable systems rely on &lt;strong&gt;Write-Ahead Logs (WALs)&lt;/strong&gt;. The idea is straightforward but essential: &lt;strong&gt;you never make a change directly; instead, you first append the change to a durable log.&lt;/strong&gt; This guarantees that no matter what happens, the system can always recover or replicate its state.&lt;/p&gt;
&lt;p&gt;In this article, we’ll explore the core principles behind the Write-Ahead Log. We’ll explain why the WAL is indispensable, how it solves problems like durability, consistency, and replication, and how its implementation differs across PostgreSQL, Kafka, and MongoDB. Yes, all of those different tools use it!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-a-write-ahead-log-wal&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-is-a-write-ahead-log-wal&quot; aria-label=&quot;what is a write ahead log wal permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;What is a Write-Ahead Log (WAL)?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 735px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f01a5436cbbca30d2b4c9be9b0bebeee/dde95/2024-12-07-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 68%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwAC/8QAFwEAAwEAAAAAAAAAAAAAAAAAAAECA//aAAwDAQACEAMQAAABzsEjQo4X/8QAGRABAAMBAQAAAAAAAAAAAAAAAQACERAh/9oACAEBAAEFAhw12HstR5//xAAWEQEBAQAAAAAAAAAAAAAAAAAAARL/2gAIAQMBAT8BrD//xAAWEQEBAQAAAAAAAAAAAAAAAAAAERL/2gAIAQIBAT8BrT//xAAaEAABBQEAAAAAAAAAAAAAAAAQAAECEjGh/9oACAEBAAY/Ajqs8uD/xAAYEAEBAQEBAAAAAAAAAAAAAAABABFRYf/aAAgBAQABPyE16yuMGYPDPJC6Zl//2gAMAwEAAgADAAAAEIDv/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARMf/aAAgBAwEBPxAHG3f/xAAXEQEBAQEAAAAAAAAAAAAAAAABABEx/9oACAECAQE/EBdLN//EAB0QAAICAgMBAAAAAAAAAAAAAAERACFBYTFRcZH/2gAIAQEAAT8QGCZAAC71FqOr5sd+QgyjWIuCMeZ+vULrI0gir2Nqf//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/f01a5436cbbca30d2b4c9be9b0bebeee/507e6/2024-12-07-cover.webp 200w,
/static/f01a5436cbbca30d2b4c9be9b0bebeee/28a80/2024-12-07-cover.webp 400w,
/static/f01a5436cbbca30d2b4c9be9b0bebeee/9144d/2024-12-07-cover.webp 735w&quot;
              sizes=&quot;(max-width: 735px) 100vw, 735px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/f01a5436cbbca30d2b4c9be9b0bebeee/37402/2024-12-07-cover.jpg 200w,
/static/f01a5436cbbca30d2b4c9be9b0bebeee/4cda9/2024-12-07-cover.jpg 400w,
/static/f01a5436cbbca30d2b4c9be9b0bebeee/dde95/2024-12-07-cover.jpg 735w&quot;
            sizes=&quot;(max-width: 735px) 100vw, 735px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/f01a5436cbbca30d2b4c9be9b0bebeee/dde95/2024-12-07-cover.jpg&quot;
            alt=&quot;2024 12 07 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;A write-ahead log follows a basic principle: &lt;strong&gt;before applying any changes to the main data store, the system writes the changes to an append-only log.&lt;/strong&gt; The log serves as a sequential, persistent record of every operation. If the system crashes midway through applying a change, the WAL can be replayed to restore the system to a consistent state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Imagine a database table where a row is being updated.&lt;/strong&gt; Without a WAL, the database might overwrite the row in the main storage. If the server crashes halfway through writing the new data during this operation, the old and the new data could be corrupted, leaving the system in an inconsistent state. The update is neither “committed” nor safely recoverable.&lt;/p&gt;
&lt;p&gt;The WAL solves this by ensuring the following sequence of events:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Log First:&lt;/strong&gt; The system writes the change (e.g., “update row X with new value”) to the write-ahead log. This log entry is &lt;strong&gt;sequentially appended&lt;/strong&gt; and written for durable storage, such as disks. &lt;strong&gt;That’s why the structure is called Write-Ahead Log, as we’re writing new entries ahead of others.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Apply Later:&lt;/strong&gt; The system applies the change to the actual data structures (e.g., tables or indexes) only after the WAL entry has been safely persisted. This step can be asynchronous and may happen with some delay since the log already guarantees the change is not lost.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Crash Recovery:&lt;/strong&gt; If the system crashes after writing the log but before applying the change, the WAL can be replayed on restart. This ensures the operation is eventually applied. The log serves as the source of truth, allowing the system to apply any uncommitted changes and restore a consistent state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This mechanism guarantees two critical things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Durability:&lt;/strong&gt; Changes are not “lost” once they are logged.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; If a crash occurs, the system can always replay the WAL to apply incomplete operations and recover to a consistent state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The write-ahead log is at the core of modern systems because it satisfies critical properties for ACID transactions, replication, and fault tolerance. It allows systems to trade off complexity in favour of simplicity—sequential appends are far easier, faster, and more reliable than attempting scattered random writes directly to a data store.&lt;/p&gt;
&lt;p&gt;For example, imagine this process in practice with a database:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; A user transaction updates the balance column of a user’s table. The system writes a WAL entry like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
  offset: 34958,
  transaction_id: 123,
  operation: {
    type: &quot;UPDATE&quot;,
    table: &quot;users&quot;,
    set: &quot;balance=100&quot;,
    where: &quot;id=1&quot;
   }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This entry is appended to the WAL file and flushed to disk. Each operation can gather a different set of metadata (update will be different than insert or delete).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; The system updates the &lt;em&gt;users&lt;/em&gt; table in its data files. If this step is interrupted (e.g., due to a power outage), the log ensures that step 1 is already durable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; On recovery, the system reads the WAL file, identifies uncommitted changes (like transaction 123), and reapplies them to the table.&lt;/p&gt;
&lt;p&gt;Each entry has its monotonic offset. Thanks to that, the WAL Processor can keep the position of the last handled offset and know which entries have not yet been processed.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-write-ahead-logs-are-everywhere&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-write-ahead-logs-are-everywhere&quot; aria-label=&quot;why write ahead logs are everywhere permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Why Write-Ahead Logs Are Everywhere&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Write-ahead logs solve some of the hardest and most fundamental problems in systems that manage state. Their ubiquity in modern databases, messaging systems, and distributed systems is no coincidence. Let’s unpack the reasons why WALs are essential:&lt;/p&gt;
&lt;h3 id=&quot;1-durability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-durability&quot; aria-label=&quot;1 durability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;1. &lt;strong&gt;Durability&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Durability ensures that once a system acknowledges a change, that change will persist—even in the face of crashes or failures. WALs make this possible by first appending changes to a persistent log. Because appends are &lt;strong&gt;sequential writes&lt;/strong&gt;, they are faster and less prone to data corruption than scattered random writes.&lt;/p&gt;
&lt;p&gt;For example, in PostgreSQL, the system will not acknowledge a transaction as “committed” until the corresponding WAL entry is safely flushed to disk. This guarantees that no committed transaction will ever be lost, even if the server crashes.&lt;/p&gt;
&lt;h3 id=&quot;2-crash-recovery&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-crash-recovery&quot; aria-label=&quot;2 crash recovery permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;2. &lt;strong&gt;Crash Recovery&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Systems can fail at any point—hardware faults, software bugs, power outages, you name it. A crash could leave the system in an inconsistent state without a reliable recovery mechanism. By writing changes to the WAL before applying them, systems can recover gracefully:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Upon restart, the system replays the WAL to “catch up” on any logged but not applied operations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Since the WAL is append-only and sequential, replaying it is fast and deterministic.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach allows systems to recover within seconds, even after catastrophic failures.&lt;/p&gt;
&lt;h3 id=&quot;3-replication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-replication&quot; aria-label=&quot;3 replication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;3. &lt;strong&gt;Replication&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Replication is the process of propagating changes from one system to another, often for high availability, fault tolerance, or read scalability. Logs are the perfect tool for replication because they provide a &lt;strong&gt;linear, ordered history of changes&lt;/strong&gt;. Replicas can replay the WAL to stay in sync with the primary system.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mongodb.com/docs/manual/core/replica-set-oplog/&quot;&gt;In MongoDB, for instance, the&lt;/a&gt; &lt;strong&gt;&lt;a href=&quot;https://www.mongodb.com/docs/manual/core/replica-set-oplog/&quot;&gt;oplog&lt;/a&gt;&lt;/strong&gt; (from &lt;em&gt;operations log -&lt;/em&gt; MongoDB’s version of the WAL) propagates changes from the primary node to the secondary nodes. The same happens in PostgreSQL replication.&lt;/p&gt;
&lt;h3 id=&quot;4-performance-and-efficiency&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#4-performance-and-efficiency&quot; aria-label=&quot;4 performance and efficiency permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;4. &lt;strong&gt;Performance and Efficiency&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Appending to a log is far more efficient than making scattered random writes to a database. Storage systems—whether spinning disks or SSDs—are optimized for sequential writes. The WAL takes advantage of this by batching and appending changes, resulting in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Faster writes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lower I/O overhead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Simplified recovery mechanisms (logs are compact and linear).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Let’s discuss a few examples of using Write-Ahead Log in different systems: PostgreSQL, Kafka and MongoDB.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;postgresql-wal-for-acid-transactions-and-replication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#postgresql-wal-for-acid-transactions-and-replication&quot; aria-label=&quot;postgresql wal for acid transactions and replication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;PostgreSQL: WAL for ACID Transactions and Replication&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;PostgreSQL’s WAL is at the core of its durability, crash recovery, and replication mechanisms.&lt;/strong&gt; It guarantees that no committed transaction is lost, even in the face of system crashes or unexpected failures, while enabling efficient replication for high availability.&lt;/p&gt;
&lt;p&gt;It follows the same principles we mentioned before:&lt;/p&gt;
&lt;h3 id=&quot;how-it-works&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-it-works&quot; aria-label=&quot;how it works permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;When a transaction in PostgreSQL modifies data:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Log First&lt;/strong&gt;: PostgreSQL writes the intended changes as &lt;strong&gt;WAL records&lt;/strong&gt; to an append-only log file before modifying any database files (heap or indexes). This includes granular details like the operation type (&lt;code class=&quot;language-text&quot;&gt;INSERT&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt;, or &lt;code class=&quot;language-text&quot;&gt;DELETE&lt;/code&gt;), the modified page, and before-and-after states if necessary. Each WAL entry is sequentially appended to the log file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flush to Disk&lt;/strong&gt;: Before PostgreSQL marks the transaction as &lt;strong&gt;committed&lt;/strong&gt;, it flushes the WAL to disk using &lt;code class=&quot;language-text&quot;&gt;fsync&lt;/code&gt;. This guarantees durability: even if the system crashes before applying the changes to the main database files, the WAL ensures the transaction can be recovered.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Apply Changes Later&lt;/strong&gt;: PostgreSQL applies the changes to the data files asynchronously during a process called &lt;strong&gt;checkpointing&lt;/strong&gt;. At each checkpoint, the dirty pages in memory (modified data) are written back to the storage to reduce reliance on WAL during recovery.&lt;/p&gt;
&lt;p&gt;If a crash happens between the WAL flush and the data file update, PostgreSQL can use the WAL to “redo” the operations that occurred after the last checkpoint.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This two-step process—logging first, applying later—ensures that no committed transaction can ever be lost.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4e762102ad375931c6dc8f61a96456d5/6f2aa/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB9ElEQVR42nWS228SYRDF+X8bX3z0wcT45D9Q04aXNqFpNJpAlF5txcaaVAoFLbAiWFrYLNBll71f2P357XJRm3iSSSY7s2fOmfkyrusShiEPEcdJxGme1F3HwzYdwiAkiiL+h4xlWTiOQ71dplQ+wA9cXNdZkSbwvYCzy1NeH+awXYs4mhdms1k6zLBtbM9L8xVhs1ej1v1M+XrA5qsqndYtUukUR9PSn7Pbu6w9foqsjBbDYgJBMBX183yeq9JHErcZO2EXYdkmuq5iWh4jXUS7w49CAVtRmAmC6sExe+tZLE1nIZyZIDXGYy42XtLMF+aEsixjGAa9u180pDrDe51Gu48jbC4R+gGVVoPip5Kw7KKqKpIkoek6o36f9y+ec76zhef7ZIbDYUro+55QZ6R7SaD2+tSFKnOsEgol3UqZy70irliRHwRMp7rojbhXFbK5ZxSPtwjE4JXlJZSRQbkx4PZbk+s3b5kO5Lnl/TP213cx1T+Wk2uPJ0NyR1mOLt4JUf7iKIKwI0u0ehWqLZmdgzbd5g0jocqZTIgExcbmNmuPnjAYKKujJG6mhk6t/ZWfd008b0GYKGzcVPny/UTsziUIIjE9Xqn2ReNVp8pJ/QOaKa4esyJdvsllnkn2py2exvIhL/M4af7r28Oef3oXU34DF+Trfc6LWEwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/4e762102ad375931c6dc8f61a96456d5/507e6/image-2.webp 200w,
/static/4e762102ad375931c6dc8f61a96456d5/28a80/image-2.webp 400w,
/static/4e762102ad375931c6dc8f61a96456d5/8d2ea/image-2.webp 800w,
/static/4e762102ad375931c6dc8f61a96456d5/68fc1/image-2.webp 1200w,
/static/4e762102ad375931c6dc8f61a96456d5/7b980/image-2.webp 1394w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/4e762102ad375931c6dc8f61a96456d5/36ca5/image-2.png 200w,
/static/4e762102ad375931c6dc8f61a96456d5/a3397/image-2.png 400w,
/static/4e762102ad375931c6dc8f61a96456d5/a331c/image-2.png 800w,
/static/4e762102ad375931c6dc8f61a96456d5/8537d/image-2.png 1200w,
/static/4e762102ad375931c6dc8f61a96456d5/6f2aa/image-2.png 1394w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/4e762102ad375931c6dc8f61a96456d5/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://materialize.com/blog/connecting-materialize-directly-to-postgresql-via-the-replication-stream/&quot;&gt;https://materialize.com/blog/connecting-materialize-directly-to-postgresql-via-the-replication-stream/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The WAL is divided into &lt;strong&gt;16 MB segment files&lt;/strong&gt;, which are stored in the &lt;code class=&quot;language-text&quot;&gt;pg_wal&lt;/code&gt; directory. PostgreSQL rotates and archives old WAL files to manage storage efficiently. Key components of the WAL include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Redo Records&lt;/strong&gt;: Detailed operations describing changes to be replayed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Transaction IDs&lt;/strong&gt;: Unique identifiers to group WAL entries belonging to the same transaction.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commit Markers&lt;/strong&gt;: Flags indicating the end of a transaction.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PostgreSQL also supports &lt;strong&gt;archiving WAL segments&lt;/strong&gt; for disaster recovery and &lt;strong&gt;point-in-time recovery (PITR)&lt;/strong&gt;, where the database can be restored to a specific state by replaying archived WAL files.&lt;/p&gt;
&lt;h3 id=&quot;crash-recovery&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#crash-recovery&quot; aria-label=&quot;crash recovery permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Crash Recovery&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Crash recovery in PostgreSQL relies entirely on the WAL. When PostgreSQL restarts after a crash:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;It scans the WAL sequentially from the last &lt;strong&gt;checkpoint&lt;/strong&gt;. Checkpoints act as recovery starting points and reduce the WAL size that needs to be processed during recovery.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Any &lt;strong&gt;unapplied or partial changes&lt;/strong&gt; recorded in the WAL are replayed to bring the database back to a consistent state.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Because the WAL is append-only and sequential, recovery is deterministic and efficient—even for large databases.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, if a transaction updated multiple rows and crashed halfway, PostgreSQL can detect the incomplete state and replay all changes in the WAL that occurred after the last checkpoint.&lt;/p&gt;
&lt;h3 id=&quot;replication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#replication&quot; aria-label=&quot;replication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Replication&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;PostgreSQL leverages its WAL for two primary types of replication: &lt;strong&gt;physical and logical&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;1-physical-replication-streaming-replication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-physical-replication-streaming-replication&quot; aria-label=&quot;1 physical replication streaming replication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;1. Physical Replication (Streaming Replication):&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In physical replication, WAL segments are streamed directly from the &lt;strong&gt;primary node&lt;/strong&gt; to the replica nodes. Replicas replay the WAL records to maintain an exact copy of the primary data at the block level.&lt;/p&gt;
&lt;p&gt;This method is fast, efficient, and ideal for high-availability setups. However, it requires the primary and replicas to have identical database structures and PostgreSQL versions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Synchronous replication&lt;/strong&gt; ensures zero data loss by requiring replicas to acknowledge WAL entries before transactions are marked as committed.&lt;/p&gt;
&lt;h4 id=&quot;2-logical-replication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-logical-replication&quot; aria-label=&quot;2 logical replication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;2. Logical Replication&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Logical replication decodes WAL records into &lt;strong&gt;logical change events&lt;/strong&gt; like SQL-level operations (e.g., &lt;code class=&quot;language-text&quot;&gt;INSERT&lt;/code&gt;, &lt;code class=&quot;language-text&quot;&gt;UPDATE&lt;/code&gt;, or &lt;code class=&quot;language-text&quot;&gt;DELETE&lt;/code&gt;). Instead of raw block-level data, these changes are replicated to subscribers more flexibly.&lt;/p&gt;
&lt;p&gt;PostgreSQL creates a &lt;strong&gt;logical replication slot&lt;/strong&gt;, which decodes WAL records into logical operations. These operations are streamed to &lt;strong&gt;subscribers&lt;/strong&gt; that can apply them selectively or transform them as needed.&lt;/p&gt;
&lt;p&gt;Logical Replication allows partial replication, such as replicating only specific tables or rows. You can replicate between databases with different schemas or PostgreSQL versions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most importantly, it can enable integration with other systems like Kafka, event processors, or analytical platforms,&lt;/strong&gt; which can simplify data migration and real-time change data capture (CDC). I wrote about it extensively in &lt;a href=&quot;/en/push_based_outbox_pattern_with_postgres_logical_replication/&quot;&gt;Push-based Outbox Pattern with Postgres Logical Replication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Logical replication is a unique feature of PostgreSQL. Other relational databases (e.g. MSSQL transaction log) are treating WAL as ephemeral structure that can be deleted after transaction is committed.&lt;/p&gt;
&lt;p&gt;If you want to Nerd-snip on PostgreSQL WAL, check &lt;a href=&quot;https://www.interdb.jp/pg/pgsql09.html&quot;&gt;Hironobu Suzuki - The Internals of PostgreSQL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/wal-intro.html&quot;&gt;PostgreSQL Write-Ahead Logging (WAL)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/sql/relational-databases/logs/the-transaction-log-sql-server?view=sql-server-ver15&quot;&gt;MSSQL transaction log&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.sqlite.org/wal.html&quot;&gt;SQLite Write-Ahead Logging&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.mysql.com/blog-archive/mysql-8-0-new-lock-free-scalable-wal-design/&quot;&gt;MySQL: New Lock free, scalable WAL design&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;kafka-logs-as-the-system&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#kafka-logs-as-the-system&quot; aria-label=&quot;kafka logs as the system permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Kafka: Logs as the System&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Kafka is built entirely around the concept of logs. In Kafka, the write-ahead log isn’t just an implementation detail—it &lt;strong&gt;is the system&lt;/strong&gt;. Kafka uses logs as its core abstraction to provide durability, ordering, and replication guarantees. The append-only nature of Kafka’s logs enables high throughput, fault tolerance, and efficient data storage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kafka has two splits of message sequences: logical and physical:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Logical split is represented by&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;topics&lt;/strong&gt;&lt;/em&gt; that can group messages based on the modules, entity types, tenants or any preferred logical split&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Partitions represent physical splits.&lt;/strong&gt; Once you set up topics, you must define the partitioning inside them. Each of them is the physical append-only log.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/af56cf29e743c0d43dd43662fc1158e9/77a0f/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHElEQVR42k1Ta2/TMBTN/0QI2D8a0gQD8RATfEBIfEF7AaNsbEVFZRMwtLXdVrV0G2kSx7GdOGmTw712EUQ6sn197/G5jwSZsZCqQJxqJIRM5WAbQ+cWYSTw8tVrPH66hp3WLkxZI9MWKflxjJAGis7OluUImIyJ4tQ4h0goCLrgy4ScJ9cR9ttf8HGvjc+dr47grwCOiYR2e+liCgRs8CSFAzsxEQcmtM90gXJWk9pyYfME7JuyGCKKUm9PJClkNcbOKcXSvcDKmqYBf7zWtIZCoqgqsLVe3NVNDak9IXO4xwgBv/Sp3cHZcIwsr1xtMikRxgnMpI98cxnZ+jLMxl3k2yvIxj1K2aCpa+ebl3MPEqVIVMCvdLqH+HZ8Al1UUJSaTAXCRMKcH0Hduwn9cAl69Q7UgyXIwaHzaWpP9PZ9C99/nuKkd4aL0SWCaaJwOhg6jC9DSsNCJDGupjE0BeuVG1Crt6Hv33KkotdFqnNHaOwMj548w8bWO7xZ36KmdX1TWrsH2NzeQffoh5OtVUZFlrC/h7B7a7AHL1DsP4clmOshipLqSTXkuqXKOqVlVcNQhgF3qH8+Qv9shCl1m5tSFCWqakZ1IfI5/kPjyAq2az9WYtEU32XDc5i7DjPcLJJiJuZSOMQZIgdJe+lsIZ9p9bNLkD6Oxyz4cBxiMJxg9OtqQUx/zgK85wYw+E6Zf6tHRZPhbTyTQub4A8x9NQPSHSogAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/af56cf29e743c0d43dd43662fc1158e9/507e6/image-3.webp 200w,
/static/af56cf29e743c0d43dd43662fc1158e9/28a80/image-3.webp 400w,
/static/af56cf29e743c0d43dd43662fc1158e9/8d2ea/image-3.webp 800w,
/static/af56cf29e743c0d43dd43662fc1158e9/68fc1/image-3.webp 1200w,
/static/af56cf29e743c0d43dd43662fc1158e9/b1ef5/image-3.webp 1340w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/af56cf29e743c0d43dd43662fc1158e9/36ca5/image-3.png 200w,
/static/af56cf29e743c0d43dd43662fc1158e9/a3397/image-3.png 400w,
/static/af56cf29e743c0d43dd43662fc1158e9/a331c/image-3.png 800w,
/static/af56cf29e743c0d43dd43662fc1158e9/8537d/image-3.png 1200w,
/static/af56cf29e743c0d43dd43662fc1158e9/77a0f/image-3.png 1340w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/af56cf29e743c0d43dd43662fc1158e9/a331c/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;how-it-works-1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-it-works-1&quot; aria-label=&quot;how it works 1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Kafka repeats the same logical pattern known from previous paragraphs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Log First:&lt;/strong&gt; Kafka producers append messages to a specific partition in a topic. Each partition is a WAL where new messages are appended at the end.Partitions are immutable—once a message is written, it cannot be modified. Each message in a partition is assigned a monotonic offset, which acts as a unique position marker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flush to Disk:&lt;/strong&gt; Kafka ensures durability by persisting messages to disk before acknowledging the write to the producer. Each Kafka broker flushes log entries to disk periodically (configurable via &lt;em&gt;&lt;a href=&quot;https://kafka.apache.org/documentation/#topicconfigs_flush.messages&quot;&gt;flush.messages&lt;/a&gt;&lt;/em&gt; or &lt;em&gt;&lt;a href=&quot;https://kafka.apache.org/documentation/#topicconfigs_flush.ms&quot;&gt;flush.ms&lt;/a&gt;&lt;/em&gt;). For stronger guarantees, Kafka producers can request acknowledgements from multiple brokers (e.g., &lt;em&gt;&lt;a href=&quot;https://kafka.apache.org/documentation/#producerconfigs_acks&quot;&gt;acks=all&lt;/a&gt;&lt;/em&gt;), ensuring data is replicated before the write is acknowledged.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Apply Changes Later:&lt;/strong&gt; Consumers read messages from the topic’s partition sequentially, starting from a specific offset. The position of a consumer within a partition is called the offset. Consumers commit their offsets to Kafka or external stores to keep track of their progress. This model allows Kafka to achieve high throughput: sequential reads and writes are fast and efficient on modern storage hardware. Consumers are responsible for state management (e.g., &lt;a href=&quot;/en/deduplication-in-distributed-systems/&quot;&gt;deduplication&lt;/a&gt; or applying the messages), which keeps Kafka lightweight.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;replication-1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#replication-1&quot; aria-label=&quot;replication 1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Replication&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Kafka achieves scalability by partitioning topics into multiple independent logs.&lt;/strong&gt; Each partition is a standalone WAL, and Kafka distributes partitions across brokers in a cluster. Partitioning enables Kafka to scale horizontally, as producers and consumers can simultaneously operate on different partitions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Messages within a partition are strictly ordered.&lt;/strong&gt; The offset ensures that consumers process messages in the same order in which they were appended. There’s no guarantee of the ordering between partitions. You can think about partitions as queues to the cashier in the department store. You can only know the order of customers handling withing a queue, but not between them.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c709bfbfafefecbda874b59fbab85bdb/77a0f/image-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC5UlEQVR42i2T+0+NcRzHzz9gJJFLi2rKMLnNsiFa0/XQ6XR6OqsQJaWYRZmG/GBr4yezmY1SnZQUHUrFKY2ikmbuEyq3pdu5n268fJ8nP7y35/l+97z2fn/en0dldYxjsbkYMduFbDhck/R+7edmZRVd3T08aHxI06MW7tU1UHPXyF1jHW3PO4W66Bv4hdXu+q9xzFYnKhk2qsAcDI/ZcU3+Ec8WOrpecr++UQDqaX/+giZTq3hvolYAH5oeU33HyOCImVGLU3xnwyJgZqsLlQwZEgdj8oGAG+8/4HrRDW5V36G8opLikhKKiku5VlRCSVkFVTW1GCpuUyZU1/AIs3A2LAzJpmS4SgY5xqexOiYU27HaOObOdWOx1xJWBSxnb6wGbWQYO0JD2LQ1mKVLffBe5sMcN3fCIqJEor/CjEMAhSwOGejiSXsnn/u+YXNNkZqSyMbAAEK2bUGvC6EgL5K05F3s1iag0Sei1erw91+B58KFREVH45z4w6gAmoW7MRloEQPteNHD63cflMgFp3O4cCaDU5lJnD2m4eqlcPLzD6E/ILH/RDJH8/LwD1iJn68v0Wo141MgMyw2pzI21dCojY+9fYp+Do6wLyWZ+IjNXC7IIidtDyfTJI5mp1J4Uc2ZwhjOFuSTnpGNl9ci4iUJu0glFzoyNjNHpZSW1jYampp59eY9ulg1wYG+nM9N5cjB/UhRYegTNCSnryMraz0ROj1x8Ul4LnBHStAL4LRSxkxk4XBY0D996efT5wGlrSS9hN/8WSz39ibQz43DqWs4nBlMoqRm9ZplBO3cQHhYKIs9PcQ843AoM3QqO6gA5exKw0JyQXGaGHw8Z+Pl4UHQWnfO5W7nytUMKosLOZ6xFylTlKPbxaJ5s4mIjMLmnFL2cCayKKXl7W96+34w8P2XuJzA1NyCwWCg3HCTW1WlPH1ymc7uUtq7WmkWy2161kG1sZbSsjLqG03YRSvKXyIKlR3+A8+ChnpDotZ6AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/c709bfbfafefecbda874b59fbab85bdb/507e6/image-4.webp 200w,
/static/c709bfbfafefecbda874b59fbab85bdb/28a80/image-4.webp 400w,
/static/c709bfbfafefecbda874b59fbab85bdb/8d2ea/image-4.webp 800w,
/static/c709bfbfafefecbda874b59fbab85bdb/68fc1/image-4.webp 1200w,
/static/c709bfbfafefecbda874b59fbab85bdb/b1ef5/image-4.webp 1340w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/c709bfbfafefecbda874b59fbab85bdb/36ca5/image-4.png 200w,
/static/c709bfbfafefecbda874b59fbab85bdb/a3397/image-4.png 400w,
/static/c709bfbfafefecbda874b59fbab85bdb/a331c/image-4.png 800w,
/static/c709bfbfafefecbda874b59fbab85bdb/8537d/image-4.png 1200w,
/static/c709bfbfafefecbda874b59fbab85bdb/77a0f/image-4.png 1340w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/c709bfbfafefecbda874b59fbab85bdb/a331c/image-4.png&quot;
            alt=&quot;image 4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Of course, the other rule is that the one you’re standing on will go the slowest. The cashier shift will change, the paper will finish, and someone will forget the PIN of their credit card. You know the drill!&lt;/p&gt;
&lt;p&gt;Kafka distributes partitions across multiple brokers, allowing it to handle very high volumes of data. Kafka replicates its logs across multiple brokers for fault tolerance. Each partition has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A leader:&lt;/strong&gt; The broker that handles, writes, and serves consumers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Followers:&lt;/strong&gt; Brokers that replicate the log.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the leader fails, one of the followers can seamlessly take over. Again, that’s why &lt;a href=&quot;https://www.architecture-weekly.com/i/147613456/single-writer-pattern&quot;&gt;Kafka uses the Single Writer pattern&lt;/a&gt;! I hope that you see things coming together!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mongodb-the-oplog-for-replication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#mongodb-the-oplog-for-replication&quot; aria-label=&quot;mongodb the oplog for replication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;MongoDB: The Oplog for Replication&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mongodb.com/docs/manual/core/replica-set-oplog/&quot;&gt;MongoDB uses a write-ahead log called the&lt;/a&gt; &lt;strong&gt;&lt;a href=&quot;https://www.mongodb.com/docs/manual/core/replica-set-oplog/&quot;&gt;oplog&lt;/a&gt;&lt;/strong&gt;. It’s a core component for durability and replication. It’s an append-only structure that records all operations modifying the data. The oplog enables MongoDB to maintain copies of data across nodes, recover from failures, and ensure eventual consistency in a cluster.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MongoDB organizes its oplog into a logical rolling log stored as a special capped collection (&lt;/strong&gt;&lt;em&gt;&lt;strong&gt;local.oplog.rs&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;).&lt;/strong&gt; It provides a compact and efficient way to propagate changes while guaranteeing idempotency—operations can be safely replayed multiple times without altering the final state.&lt;/p&gt;
&lt;p&gt;It’s only available for clustered configurations. It doesn’t work for single instances, as no replication is needed there. The goal is not an audit log. It’s tailored for replication and recovery in &lt;a href=&quot;https://www.mongodb.com/docs/manual/replication/&quot;&gt;replica sets&lt;/a&gt;. The &lt;em&gt;oplog&lt;/em&gt; is critical for ensuring &lt;strong&gt;eventual consistency&lt;/strong&gt;, enabling high availability, and supporting MongoDB’s asynchronous replication model.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Oplog&lt;/em&gt; works similarly to PostgreSQL WAL in terms of logical replication. It records &lt;strong&gt;high-level operations&lt;/strong&gt; such as inserts, updates, and deletes, ensuring idempotency.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 337px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f58a71c2591628dd599902cb3d172517/de2ac/image-5.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAJABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFAgP/xAAWAQEBAQAAAAAAAAAAAAAAAAACAQP/2gAMAwEAAhADEAAAAau5rhPUQNZ//8QAGhAAAgMBAQAAAAAAAAAAAAAAAgMAATIRMf/aAAgBAQABBQKmDCvkpoQMu2rz/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAEDAQE/AY1//8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERAv/aAAgBAgEBPwHTjIj/xAAaEAACAgMAAAAAAAAAAAAAAAAAAQJxETEy/9oACAEBAAY/AujDZsVkR2f/xAAbEAABBAMAAAAAAAAAAAAAAAABABFBUSAhgf/aAAgBAQABPyE3YlSC1i9owT5hJT//2gAMAwEAAgADAAAAEFwf/8QAFhEBAQEAAAAAAAAAAAAAAAAAAREA/9oACAEDAQE/EFS4U3//xAAXEQEBAQEAAAAAAAAAAAAAAAABABFB/9oACAECAQE/EEQOZO1//8QAHBAAAgEFAQAAAAAAAAAAAAAAAREAECExobFB/9oACAEBAAE/ECt+Chxi4CDw2I/4imwm04KDacE//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/f58a71c2591628dd599902cb3d172517/507e6/image-5.webp 200w,
/static/f58a71c2591628dd599902cb3d172517/8c264/image-5.webp 337w&quot;
              sizes=&quot;(max-width: 337px) 100vw, 337px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/f58a71c2591628dd599902cb3d172517/37402/image-5.jpg 200w,
/static/f58a71c2591628dd599902cb3d172517/de2ac/image-5.jpg 337w&quot;
            sizes=&quot;(max-width: 337px) 100vw, 337px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/f58a71c2591628dd599902cb3d172517/de2ac/image-5.jpg&quot;
            alt=&quot;How to increase OpLog Size in replica set in Mongo 3.2&quot;
            title=&quot;How to increase OpLog Size in replica set in Mongo 3.2&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://www.linkedin.com/pulse/how-increase-oplog-size-replica-set-mongo-32-garima-masand/&quot;&gt;https://www.linkedin.com/pulse/how-increase-oplog-size-replica-set-mongo-32-garima-masand/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;how-it-works-2&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-it-works-2&quot; aria-label=&quot;how it works 2 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;MongoDB’s &lt;em&gt;oplog&lt;/em&gt; follows a familiar pattern:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Log First:&lt;/strong&gt; when a write operation occurs (e.g., an insert, update, or delete), MongoDB writes a logical description of the change to the &lt;em&gt;oplog&lt;/em&gt;. For example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
  &quot;ts&quot;: Timestamp(1626900000, 1),  // Time of the operation
  &quot;op&quot;: &quot;i&quot;,                      // &apos;i&apos; indicates an insert
  &quot;ns&quot;: &quot;users.profile&quot;,          // Namespace (db.collection)
  &quot;o&quot;: { &quot;_id&quot;: 1, &quot;name&quot;: &quot;Alice&quot; } // Document being inserted
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each entry is timestamped and immutable, ensuring order and durability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Apply Later:&lt;/strong&gt; The &lt;em&gt;oplog&lt;/em&gt; is persisted alongside MongoDB’s data files to ensure durability. If the primary node crashes, the &lt;em&gt;oplog&lt;/em&gt; allows the database to recover by replaying the operations sequentially.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Replication:&lt;/strong&gt; Secondary nodes continuously pull new &lt;em&gt;oplog&lt;/em&gt; entries from the primary. These entries are applied in the same order as they were written, ensuring the secondary remains an up-to-date copy of the primary. If a secondary disconnects temporarily, it can catch up using older &lt;em&gt;oplog&lt;/em&gt; entries if they are still within the &lt;em&gt;oplog&lt;/em&gt; size.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;oplog-and-durability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#oplog-and-durability&quot; aria-label=&quot;oplog and durability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Oplog and Durability&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The oplog ensures that data remains consistent and recoverable even when nodes fail:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Durability&lt;/strong&gt;: Every write operation is first recorded in the &lt;em&gt;oplog&lt;/em&gt; before being applied to the main database. If a node crashes, MongoDB can &lt;strong&gt;replay the&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;oplog&lt;/strong&gt;&lt;/em&gt; to restore a consistent state—much like rewinding changes step by step.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Replication Lag&lt;/strong&gt;: Secondaries keep up by copying changes from the primary’s &lt;em&gt;oplog&lt;/em&gt;. But if a secondary falls too far behind, it risks missing entries once the &lt;em&gt;oplog&lt;/em&gt; overwrites older operations. When this happens, the node becomes &lt;strong&gt;stale&lt;/strong&gt; and requires a full resync.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This asynchronous model ensures high availability with minimal impact on write performance. However, MongoDB introduces flow control to throttle the write rate if secondaries start to lag, preventing them from falling too far behind.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;oplog-size-and-the-window-of-recovery&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#oplog-size-and-the-window-of-recovery&quot; aria-label=&quot;oplog size and the window of recovery permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Oplog Size and the Window of Recovery&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;MongoDB’s oplog has a &lt;strong&gt;fixed size&lt;/strong&gt;—by default, about &lt;strong&gt;5% of free disk space&lt;/strong&gt;. It works like a &lt;strong&gt;circular buffer&lt;/strong&gt;: new operations overwrite the oldest entries when the oplog fills up. The time span between the oldest and newest entries is called the &lt;strong&gt;oplog window&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Why does this matter?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A &lt;strong&gt;larger oplog&lt;/strong&gt; means a longer recovery window. Secondaries can catch up even after extended downtime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Heavy write workloads, like bulk updates or frequent deletes, can consume oplog space faster, reducing the window and increasing the risk of stale nodes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You need to be careful, as each small change, even tiny atomic incrementation, generates oplog entries. If your oplog is too small, these frequent updates will quickly push older operations out of the log.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;TLDR&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The write-ahead log (WAL) is everywhere.&lt;/strong&gt; The simple idea powers reliability in databases, messaging systems, and distributed systems. PostgreSQL uses it to guarantee ACID compliance and keep replicas in sync. Kafka takes it further, making the log the entire system, scaling through partitions and replication. MongoDB’s &lt;em&gt;oplog&lt;/em&gt; follows the same principle but focuses on propagating high-level operations to maintain consistency across nodes.&lt;/p&gt;
&lt;p&gt;At the core, they all follow the same rule: log first, apply later. This guarantees that no matter what—crashes, failures, or delays—systems can recover, replicate, and keep their state intact. Whether it’s replaying changes after a crash, distributing events to consumers, or syncing data across a cluster, the log makes it possible.&lt;/p&gt;
&lt;p&gt;Knowing how this pattern works—and how tools like PostgreSQL, Kafka, and MongoDB implement it—isn’t just trivia. It’s the key to understanding how modern systems handle failures, scale writes, and ensure data consistency. If you’re building anything reliable, you’re already building on this concept—it’s time to know it well.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Applying Observability: From Strategy to Practice with Hazel Weakly]]></title><description><![CDATA[Have you considered applying observability but struggled to match the strategy with the tooling? Or maybe you were lost on how to do it? I…]]></description><link>https://event-driven.io/en/applying-observability-from-strategy/</link><guid isPermaLink="false">https://event-driven.io/en/applying-observability-from-strategy/</guid><content:encoded>&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Webinar recording&quot; src=&quot;https://www.youtube-nocookie.com/embed/d2eVzz3oxJY?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Have you considered applying observability but struggled to match the strategy with the tooling? Or maybe you were lost on how to do it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I had a great discussion with &lt;a href=&quot;https://hazelweakly.me/about/&quot;&gt;Hazel Weakly&lt;/a&gt; about Observability beyond Open Telemetry, keeping it “meaty” with practical thoughts.&lt;/strong&gt; I learned a lot, and I’m sure you will, too! Just check the recording!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hazel is one of the best people in the observability space.&lt;/strong&gt; I’ve been following her work for a long time. Don’t believe me? Check her works, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://hazelweakly.me/blog/redefining-observability/&quot;&gt;Redefining Observability&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://thenewstack.io/the-4-evolutions-of-your-observability-journey/&quot;&gt;The 4 Evolutions of Your Observability Journey&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://thenewstack.io/opentelemetry-challenges-handling-long-running-spans/&quot;&gt;OpenTelemetry Challenges: Handling Long-Running Spans&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://hazelweakly.me/talks/kubecon-na-2024/&quot;&gt;OpenTelemetry: Bridging Platform and Enablement&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://hazelweakly.me/talks/monitorama-pdx-2023/&quot;&gt;Monitoring Mastodons - A Story about Hachyderm&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Of course, Hazel is more than that and spends her days working on building out teams of humans as well as the infrastructure, systems, automation, and tooling to make life better for others.&lt;/strong&gt; She’s worked at a variety of companies, across a wide range of tech, and knows that the hardest problems to solve are the social ones. Hazel currently serves as a &lt;strong&gt;Director on the board of the Haskell Foundation&lt;/strong&gt; and is fondly known as the &lt;strong&gt;Infrastructure Witch of Hachyderm&lt;/strong&gt; (a popular Mastodon instance). She also created the official Haskell “setup” Github Action and helps maintain it. She enjoys traveling to speak at conferences and sharing what she’s learned with others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I first saw her when I joined the &lt;a href=&quot;https://hachyderm.io/&quot;&gt;Hachyderm Mastodon instance&lt;/a&gt;.&lt;/strong&gt; She was the person trying to handle the sudden massive traffic spike of people transitioning from Twitter. Trying to keep Postgres and Ruby-based systems alive in the on-premise basement installation.&lt;/p&gt;
&lt;p&gt;Sounds familiar? Yet, it’s a similar story to our cases when we underprovision our systems, trying to recover unexpected traffic quickly. It’s hard without the system being observable. It was a nice starting point for our discussion, but we covered much more. We went through, e.g.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;What’s even observability?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How do we define observability strategy?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How do we apply it in practice?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Hazel hints at how to set up observability in existing and new systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How to be reasonable and build observability from different tools, metrics, traces, and logs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How to make organisation change and facilitate tech vs product discussions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Event-Driven vs Observability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And much more!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;It’s a bold statement, but I believe you’ll have difficulty finding more actionable guidance on this topic than Hazel gives in this recording.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Warning: we went beyond Open Telemetry; if you never had contact with it, it’d be great if you checked other resources, e.g. Hazel articles or previous editions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/when-logs-and-metrics-arent-enough/&quot;&gt;When Logs and metrics are not enough: Discovering Modern Observability&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/making-your-system-observability/&quot;&gt;Making your system observability predictable&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/why-to-measure-and-make-our-system/&quot;&gt;Why to measure and make our system observable? How to reason on chaotic world&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nevertheless, I believe that the talk was accessible and also filled with a great piece of advice for all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why we do not have a paywall here; we’d like you to benefit from it!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Please leave the comments and share them with friends. We’d like to hear your thoughts!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;webinars&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#webinars&quot; aria-label=&quot;webinars permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Webinars:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing&quot;&gt;#1 - From CRUD to Event Sourcing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short&quot;&gt;#2 - Keep your streams short! Or how to model Event-Sourced systems efficiently&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;#3 - Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice&quot;&gt;#4 - From CRUD to CQRS in practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100&quot;&gt;#5 - Architecture Weekly 100 Edition - Live Q&amp;#x26;A&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;#6 - Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven&quot;&gt;#7 - Design and test Event-Driven projections and read models&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates&quot;&gt;#8 - Slim down your aggregates!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;#9 - Radek Maziarka - Modularization with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;#10 - PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski&quot;&gt;#11 - Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify&quot;&gt;#12 - Jeremy D. Miller: Simplify your architecture with Wolverine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic&quot;&gt;#13 - Yves Goeleven - The Fantastic 9&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-14-mateusz-jendza-why-verified&quot;&gt;#14 - Mateusz Jendza: Why Verified Credentials is the Future of Digital Identity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-15-mario-bittencourt-leveraging&quot;&gt;#15 - Mário Bittencourt: Leveraging BPMN for Seamless Team Collaboration in Software Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-1-sagas-hector-garcia&quot;&gt;#16 - Papers We Love #1 - Sagas (Hector Garcia-Molina, Kenneth Salem)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-16-simple-patterns-for-events&quot;&gt;#17 - Simple patterns for events schema versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-17-andrea-magnorsky-introducing&quot;&gt;#18 - Andrea Magnorsky: Introducing Bytesize Architecture Sessions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-18-laila-bougria-debug-your&quot;&gt;#19 - Laïla Bougriâ: Debug your thinking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;#20 - Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;#21 - Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-22-on-performance-testing/&quot;&gt;#22 - On Performance Testing with Jarosław Pałka&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;#23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/frontent-architecture-backend-architecture/&quot;&gt;#24 - Frontent Architecture, Backend Architecture or just Architecture? With Tomasz Ducin&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;#25 - Applying Observability: From Strategy to Practice with Hazel Weakly&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Deduplication in Distributed Systems: Myths, Realities, and Practical Solutions]]></title><description><![CDATA[Last week, we discussed how to use queuing to guarantee idempotency. What’s idempotency? It’s when we ensure that no matter how often we run…]]></description><link>https://event-driven.io/en/deduplication-in-distributed-systems/</link><guid isPermaLink="false">https://event-driven.io/en/deduplication-in-distributed-systems/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Last week, &lt;a href=&quot;/en/ordering-grouping-and-consistency/&quot;&gt;we discussed how to use queuing to guarantee idempotency&lt;/a&gt;. What’s idempotency?&lt;/strong&gt; It’s when we ensure that no matter how often we run the same operation, we’ll always end up with the same result. In other words, duplicates won’t cause inconsistency. Read more details in &lt;a href=&quot;/en/idempotent_command_handling/&quot;&gt;Idempotent Command Handling&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Duplication is inevitable in distributed systems. Message can be sent multiple times due to retries, a broker crashes and starts redelivery, or redundancy is introduced by high-availability mechanisms.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deduplication is the process of identifying and eliminating duplicate messages to ensure systems handle them only once.&lt;/strong&gt; On paper, it seems like a straightforward solution to a common problem. In practice, it involves trade-offs that influence scalability, performance, and reliability. It is not a cure-all but a piece of a larger trade-off puzzle.&lt;/p&gt;
&lt;p&gt;In this article, we’ll discuss the complexities of deduplication. We’ll see how it’s implemented in technologies like Kafka and RabbitMQ and discuss why &lt;strong&gt;exactly-once delivery&lt;/strong&gt; is a myth. We’ll also explore &lt;strong&gt;exactly-once processing&lt;/strong&gt;—the achievable goal that lies at the intersection of deduplication and idempotency.&lt;/p&gt;
&lt;p&gt;Again, as in this article series, we’ll showcase that on simple but real code!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s a free edition this time, so there’s no paywall!&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;Mastering Database Connection Pooling&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;Queuing, Backpressure, Single Writer and other useful patterns for managing concurrency&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-191-is-it-production/&quot;&gt;Is It Production Ready?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-191-is-it-production/&quot;&gt;Ordering, Grouping and Consistency in Messaging systems&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Plus all the webinars, &lt;a href=&quot;https://www.architecture-weekly.com/p/webinars&quot;&gt;check them out here&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Sounds fair, isn’t it? For FREE is always a decent price!&lt;/p&gt;
&lt;p&gt;Feel free to share the link with your friends so they can also benefit!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Ok, enough of the commercial break! Let’s get back to duplication!&lt;/p&gt;
&lt;h2 id=&quot;why-do-duplicate-messages-occur&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-do-duplicate-messages-occur&quot; aria-label=&quot;why do duplicate messages occur permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Why Do Duplicate Messages Occur?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Duplication in distributed systems is not a bug; it is an expected side effect of ensuring reliability and fault tolerance. Let’s discuss the most common scenarios for messaging systems.&lt;/p&gt;
&lt;h3 id=&quot;retries-without-acknowledgment&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#retries-without-acknowledgment&quot; aria-label=&quot;retries without acknowledgment permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Retries Without Acknowledgment&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Let’s start with network retries. Suppose an IoT system sends messages with device status updates to a general processing system located in the cloud. It’s common for such scenarios to be temporarily disconnected. Due to a temporary network issue, the broker’s acknowledgement doesn’t arrive to the message producer within the expected time frame. The producer is designed to ensure reliability. It caches the message and retries delivery to the broker. As a result, the broker receives the same message twice.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 570px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9320f06614974e7557cb931d55c3757b/629d6/2024-11-25-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC0UlEQVR42k1Uy3LbRhDE5/hz8in5j1xzyNWXVOWQg3PLJVUuOalKYodiRRRJ8YXnvrCLXQIUKMmy270LWsmha7CPme3pmUGmWg9nBDqrCIm26+HCGa2/hw33MO4EYQJq5ROkOXJv4PkJlnCtgm8beCugjEOmtcaTucVHPaNdQiqBWtOx7VFJQ6cxBbb+/iVQfCxCmQ6jXuHZzIkbKFkha43Ex1ACg8BzqLBeLjF/8wNmr7/F9bsruOEJq4PCcqfQkJ0Np4ldGKFbh4euAnqRoGUdGTYpEEaPz/cGq+s5fv7uG/zy/Sv89tOPWBwMNoXGrrL4e1FSkilVQ+aN0jjHgGcPnAyUSAElnto1PoUczz5HXh3w5+9X+Ovtr5iv7jBbVpjdGbxfCnxYSdwVFt0xpjwyZYvR5vgcJlwYajyof/BUX9HOYLSCdNTHT7qV0uF2J5iyxmwtuWYRqWlLCNVOGoo/qOGMGjbInG0Ryhn66hqhnjOggO8f0dGhO444nh6JBwQirsPwwPMzOsLYDq66wSAX6JsbMmTArh8hbMCy3EG7AH86pwB+OCfnr9/dBTFwemCYHol6aj9Q0yEVLHOslvFHbOUW7ngiizM8mSSdOl6McNH20Gl9mvaIeCf0Y2IeWcfqZ8r02NUN/i0WyIVm8wb2n0uoVcdKelSE0OGlwdOe7C73aHkv9m4pYmPbAY3osM9rKH2EsmRCxAlJzNzEpr0UIjHkno7n/JZxeghFIg0Llim+vL8tUW8lDqsqBY+6ThNx6Tk6xsrGZo59mBAnpT1iR99y3SBflihKhaysWmwXBYq7mjbHnlMhyTCmpjh+mmwks1D2P/sVRW2xuclxYK9GMpstq6zoGBfVpkFJ1I1NVY0VMxeWiSGFd/9jGM+loe+6TtlVG4Ei18gExdxtahwYbM8XouBR1xe4/vJTmH4IUVudNByS1jv67Jld9C1qgy/ZjFzYvzdBFAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/9320f06614974e7557cb931d55c3757b/507e6/2024-11-25-cover.webp 200w,
/static/9320f06614974e7557cb931d55c3757b/28a80/2024-11-25-cover.webp 400w,
/static/9320f06614974e7557cb931d55c3757b/a7be0/2024-11-25-cover.webp 570w&quot;
              sizes=&quot;(max-width: 570px) 100vw, 570px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/9320f06614974e7557cb931d55c3757b/36ca5/2024-11-25-cover.png 200w,
/static/9320f06614974e7557cb931d55c3757b/a3397/2024-11-25-cover.png 400w,
/static/9320f06614974e7557cb931d55c3757b/629d6/2024-11-25-cover.png 570w&quot;
            sizes=&quot;(max-width: 570px) 100vw, 570px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/9320f06614974e7557cb931d55c3757b/629d6/2024-11-25-cover.png&quot;
            alt=&quot;2024 11 25 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This scenario shows a fundamental trade-off: while retries improve reliability, they also introduce the potential for duplicates.&lt;/strong&gt; Without deduplication mechanisms, the same message might be processed multiple times, potentially triggering duplicate actions and/or inconsistent state.&lt;/p&gt;
&lt;h3 id=&quot;failures-during-processing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#failures-during-processing&quot; aria-label=&quot;failures during processing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Failures During Processing&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Even when a message is successfully delivered, failures during processing can lead to duplicates. For instance, imagine a task queue system where a consumer retrieves and begins processing a message. The consumer crashes before completing the task and sending an acknowledgement to the broker. When the broker doesn’t receive the acknowledgement, it assumes the task wasn’t processed and redelivers the message to another consumer—or the same one after recovery.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 570px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/793444ca3094e0ebed4b539cd148ba99/629d6/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC50lEQVR42lVUuZLcNhScX9Kv+F/8FUocuxzbsarsQI6UKNBKWru0O5zh7M7wxkmCN+fYldoNzEpVDrrw8IBuvgOPK1vXWMwOJ7PFomNYa2C7M3S7oOkW1O1MTDBuhKoHWO5te+SdI0wzYNKeuwnQSmCljcKlecC3eoOnZg+tFYbGYLIZlNJo+jNK3SGTLYUW6LrDYEuigJQSpybBtybGV/cIJUqsaqvw3BXA0uC5FxSpILItZHILbTQK1SPaC2wThW1qYZsGKl9DEpWocG4rYLZgqNCSgloLPLuUgi2+DhJp8YD7LEZUbLDNM9xuSsS5Q5w1+BRJ2gLrPMKa5+vDHY51HoLBKKG8oDEGJ/UvLvIDzmYdUpasVSYkhGmxSw0+3OW4uS9wu6VgomHqHqlQSMuS9X/ARdzgSX+hYIWVZVpLeYNz8Z7rxxC2r5vrjwHtsHCd2ZQp1NH1C30ndNMTbO0wyTUu5h9c9N21hq4faOTI8h3FMhJ6dCMFhyPXI/rpTDIxnzEsF9on9OM5rNb1SJIUu5vPiKOIDXNYBTIJO5GgX57QzxeM8wkD0Y2nINqOjJKRNj664FsoekTNSD+9+QN//vIK7379GUnVsob1xHoIxNWWHbXhrUnbhfpV2qMLkKYP8LYgSuWg3Iz3b//C299+wt+/v0a0y9nleuQ7q3G/j4OIIElQSJoOyvZMYwgf0W4KdVR8zMHHM8U7uXT4+OUO+0IjK60XZEcfBXThULCD/qJPreaUhKmgiEfwEeZlb8LEzEj3iu+S3INGWdVYCdHgEGVI4wL7dYY8N4EkfXTNeCV/F3Hz/+yS0XlO4G8KHA4cvUrQGeUoGGVCpxe03RzETDOFKLxI/RKRF7Lualeq/cHN4hIHTtTK1yrZlcgf6Hzk2Kk2vD8vYAKuog19Htefw9Xny5XsqiBY7CUy1nHlO5fkGknKl8+1pKD/8rWzXUjd31EvzQn74Luep8xozzlPMj85Fv8Bmwha4lO8feoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/793444ca3094e0ebed4b539cd148ba99/507e6/image-2.webp 200w,
/static/793444ca3094e0ebed4b539cd148ba99/28a80/image-2.webp 400w,
/static/793444ca3094e0ebed4b539cd148ba99/a7be0/image-2.webp 570w&quot;
              sizes=&quot;(max-width: 570px) 100vw, 570px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/793444ca3094e0ebed4b539cd148ba99/36ca5/image-2.png 200w,
/static/793444ca3094e0ebed4b539cd148ba99/a3397/image-2.png 400w,
/static/793444ca3094e0ebed4b539cd148ba99/629d6/image-2.png 570w&quot;
            sizes=&quot;(max-width: 570px) 100vw, 570px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/793444ca3094e0ebed4b539cd148ba99/629d6/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this case, the broker fulfils its promise of at-least-once delivery. However, without safeguards, the system risks performing the same operation twice, such as issuing a refund or updating a shared state.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;internal-resiliency-in-messaging-tools&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#internal-resiliency-in-messaging-tools&quot; aria-label=&quot;internal resiliency in messaging tools permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Internal resiliency in messaging tools&lt;/h3&gt;
&lt;p&gt;Messaging systems are internally built to be resilient; they should survive internal failures. While recovering from them, they may be doing retries to ensure that messages are delivered.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 594px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6027c8a9eea44c83dda76a6e1d60f040/ff7bb/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC9UlEQVR42k1UPY/kRBD1TyHghyAh/g4JGTGQIWKSk0AiOAhOSCR3EgFI3KFFsOzusOedGY897rbbbrs/bM8Xu7f3eN0zcyIo1VS56tWrj55E6w6+rzBQbCeh3YTO76Epndui6QY02qFuLarGQtHu7IYxW37fwGrJXAnflYzrkZi2xIO+wiNl383pNHB+Qm8segIWUiEtFZrxLe7yNYRs0PtdLKbNiK26Ye4NHppL6LYmoC7xOFbARuPe11CqxqTmsMVfSO/ukKVrXHz3La6++RKXL36G6DwZTifAAfu+iLmPg0DXKiS9KvB2VMDe4V9XIRcrLNYpluU/mGUlnn/+KZ5+8R6ef/8+nn38EV7+dkWwALjjONwR8OCJUUM3EXCFNzajQ+JgCra4xrKSuBNLZLXGs6+f4MevPsCrHz7E088+wcWfKUcxnebrsdMLYJARo40MmwL36nc8yF/48RZtZ5iwi/MLy7hd1Zj9+gp//PQCLy8LpPMlYzzMsEfbD9jWV3jD/Ht1Aa1bAnIpZv03nLiGETN0XIYdD0zYwU8H2GGDsunQjmHrI+09XPBPtO2Ifn0NX6dw8oaADRI37qD6HlmVMXjCsD1gpAQ9bA4RdGCyn3YRKPjeCWMcCxrmGU9wXkdihwOUtcianMz2kZllERPujNvU/5Nwd+EGj78nEtjCMSfkOXYV5pooPfLOVnhdphDKQDYOUln+DofsULUu6loPUWSwW884G+NF0BRJX1kbJOE4ZdWjKGsOeURLW5vpxOTEhron83DQZ582R7YNizQEa7Tna/JIhOiQvxaoVy1WqYigdji+hNjiqU1zAozPjr5wBS0Lz69zyEWNbMZXJLjlYqUIWKKizqhLslXdyPY8wY8s28DKnXQQsgtgQjksbnKIZY2CZFbESESpkd+WEKxSzGVsIQy58yd2kek23p3xu3d2+HMI41nOCsilQsEuhdBIwvzyZYWSB5yzQtMdZ3hmcZ5hADu3fPYHnS0qrLM6Yoiqw38IkVcJOh36lQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/6027c8a9eea44c83dda76a6e1d60f040/507e6/image-3.webp 200w,
/static/6027c8a9eea44c83dda76a6e1d60f040/28a80/image-3.webp 400w,
/static/6027c8a9eea44c83dda76a6e1d60f040/5258d/image-3.webp 594w&quot;
              sizes=&quot;(max-width: 594px) 100vw, 594px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/6027c8a9eea44c83dda76a6e1d60f040/36ca5/image-3.png 200w,
/static/6027c8a9eea44c83dda76a6e1d60f040/a3397/image-3.png 400w,
/static/6027c8a9eea44c83dda76a6e1d60f040/ff7bb/image-3.png 594w&quot;
            sizes=&quot;(max-width: 594px) 100vw, 594px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/6027c8a9eea44c83dda76a6e1d60f040/ff7bb/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To do that, they often employ redundancy to ensure fault tolerance.&lt;/strong&gt; Message is usually redistributed between multiple nodes. Usually, a single node is used as a leader to orchestrate the message delivery. But what if a leader fails? What if it restarts or another becomes a leader? Even in such cases, messages should be delivered. All the strategies to deal with that can cause duplicate handling.&lt;/p&gt;
&lt;p&gt;These examples illustrate that duplication isn’t a bug—it’s a consequence of distributed systems prioritizing reliability over strict delivery guarantees.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why it’s essential to understand patterns like Outbox and Inbox. I wrote about them longer in &lt;a href=&quot;/en/outbox_inbox_patterns_and_delivery_guarantees_explained/&quot;&gt;Outbox, Inbox patterns and delivery guarantees explained&lt;/a&gt;.&lt;/strong&gt; They are tools to get the at-least-once delivery and exact-once processing guarantees.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 594px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/f0d3233e9478b0f03690b3d761f7ab0d/ff7bb/image-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAADEElEQVR42j2UW3ObSBSE9f9/Uap29yH2uuzEiWRZwkYGAcNlZrgjhCxt7OTbwziVh1PMUJyePt09LCqj6PON1JbWBFT9ifpPTVTthKkPFOVcA7YeqbqJeji5Z6f3DNpzGNYaFq0AvWfXvBc3HNM7aR5oDxeaGVAa2uGVZvg4oPlddTfvX+WwA5O64z2X/vyKWscsOr2DPoax4NVsKZueTgAdwDBRVCOPLwXb0JALQwfuGAqgfPuqN9KrBSOikWkXXf4EXQhHzUkAtW1dwwfYgQc/I7U9O1Vx/5Q5QMdSGNq6FcBH6TUOoyqEYZtu+Vlt+NUGTMVaGA5/xkx1x8rX3G4K7j3N/bNmn3d0MzsBtXXHKfvuen8JRqWTmaHHW/IXb+ofpnyJqfrfWs36ndilGYEq2SU1j4GlbI8iyXzgq6xFw+SKH/En3tXfAigj15lPG1zTxV+okwenXTde6EXHrDYEZUR/vBDZhOYwcpB1P57du1lvE/zLoJa0+xvKeeR+PKFsTpAH9K3l2BsO48ThdCHQASpZMwwDcWkJs2dataaX/fH8k72JCLKdsB2l/wXbdDKyMEkqzU6Y9rlUuqZpWgqJjx9HmGgr+bJOs62sk1yMEdNsd2QbLKni72KkJSoT6WlYmDkWyRPrZCMfV6iiJspqrpYxS68gswcXF9scxRCNl/rktuN2nXCzCklSQ1yU8t4jTAsWpSQ/VoZIFRLUkwg9oU2H96K5vn3AD2LR9OxMaCQqXuyT6ZInMejqbst6+0JZy2FxTiHxWgTPGbtve8KHhOeHyIGN3cjb5QeZsuSyn92eAWezColSW/a8nc+YzLK63hJvUvyvIVEoDP1NQiBA0UbhfQtIi4ZJ0n/RX+VuFxKjyek339v5WYgEB7ld/5l7ahl/8yUgEVLBKmb3nLII/ZlhyF4Y+isxoRo4m6UE9ZamUJhmciFvJXtz/kx1lIvhQfWZRnkCGDoyM0YUahZxLFnzEuJdxj7IybU4nOzFXZ9UaRdkdxVFv/kGzX+bLImwiY/aCytPET59lEpL/gctAFMagkZpOgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/f0d3233e9478b0f03690b3d761f7ab0d/507e6/image-4.webp 200w,
/static/f0d3233e9478b0f03690b3d761f7ab0d/28a80/image-4.webp 400w,
/static/f0d3233e9478b0f03690b3d761f7ab0d/5258d/image-4.webp 594w&quot;
              sizes=&quot;(max-width: 594px) 100vw, 594px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/f0d3233e9478b0f03690b3d761f7ab0d/36ca5/image-4.png 200w,
/static/f0d3233e9478b0f03690b3d761f7ab0d/a3397/image-4.png 400w,
/static/f0d3233e9478b0f03690b3d761f7ab0d/ff7bb/image-4.png 594w&quot;
            sizes=&quot;(max-width: 594px) 100vw, 594px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/f0d3233e9478b0f03690b3d761f7ab0d/ff7bb/image-4.png&quot;
            alt=&quot;image 4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Today, though, we’ll discuss how and if queues can help us handle duplicates to understand how reliable those mechanisms are.&lt;/p&gt;
&lt;h2 id=&quot;where-can-deduplication-happen&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#where-can-deduplication-happen&quot; aria-label=&quot;where can deduplication happen permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Where Can Deduplication Happen?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Deduplication can occur at various points in the message flow, each with its own advantages, trade-offs, and implementation complexities.&lt;/p&gt;
&lt;h3 id=&quot;producer-side-deduplication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#producer-side-deduplication&quot; aria-label=&quot;producer side deduplication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Producer-Side Deduplication&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;One way to address duplication is at the source: the producer. By ensuring that each message has a globally unique identifier, producers allow downstream systems to detect and discard duplicates. For example, a producer might use a combination of a UUID and a timestamp to create a unique key for each message.&lt;/p&gt;
&lt;p&gt;This approach works well in systems where the producer has sufficient control over message creation and retry logic. For example, a financial trading system could assign a unique transaction ID to every trade message. Even if the trade is retransmitted, downstream systems can use the transaction ID to identify and ignore duplicates.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However, producer-side deduplication isn’t the Holy Grail.&lt;/strong&gt; The broker must support deduplication logic that leverages these unique identifiers. Additionally, the producer must store the identifiers of sent messages until they receive confirmation that they’ve been processed, adding state management overhead.&lt;/p&gt;
&lt;h3 id=&quot;broker-side-deduplication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#broker-side-deduplication&quot; aria-label=&quot;broker side deduplication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Broker-Side Deduplication&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Some messaging systems, such as RabbitMQ Streams and Azure Service Bus, provide built-in deduplication mechanisms. When a message arrives, the broker compares its identifier against a deduplication cache. If the identifier matches one in the cache, the message is discarded. Otherwise, it’s processed, and the identifier is added to the cache.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Broker-side deduplication offloads the responsibility of producers and consumers, simplifying their implementations. However, it comes with its own trade-offs.&lt;/strong&gt; Maintaining a deduplication cache consumes memory or storage, especially in high-throughput systems. To prevent unbounded growth, brokers typically impose a time-to-live (TTL) on deduplication entries, meaning duplicates arriving outside this window won’t be detected.&lt;/p&gt;
&lt;p&gt;For example, RabbitMQ’s message deduplication relies on message IDs stored in an in-memory cache. This works well for short-lived messages in task queues but struggles with long-running processes or high-cardinality workloads.&lt;/p&gt;
&lt;h3 id=&quot;consumer-side-deduplication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#consumer-side-deduplication&quot; aria-label=&quot;consumer side deduplication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Consumer-Side Deduplication&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;When the producer or broker doesn’t handle deduplication, it falls to the consumer.&lt;/strong&gt; &lt;a href=&quot;/en/idempotent_command_handling/#generic-idempotency-handling&quot;&gt;Consumers can maintain their own deduplication stores,&lt;/a&gt; often using databases or distributed caches like Redis to track processed messages. Before processing a message, the consumer checks whether its identifier exists in the store. If it does, the message is skipped. If not, the consumer processes the message and adds its identifier to the store.&lt;/p&gt;
&lt;p&gt;For example, a fraud detection system might log every processed transaction ID in a Redis set. If a duplicate transaction ID arrives, the system can detect and ignore it.&lt;/p&gt;
&lt;p&gt;This approach provides flexibility but adds complexity. Consumers must balance deduplication store durability and performance. For instance, using an in-memory store like Redis offers fast lookups but requires periodic persistence to avoid data loss during crashes.&lt;/p&gt;
&lt;h3 id=&quot;trade-offs-and-challenges-of-deduplication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#trade-offs-and-challenges-of-deduplication&quot; aria-label=&quot;trade offs and challenges of deduplication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Trade-Offs and Challenges of Deduplication&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Deduplication isn’t free. Each implementation choice involves trade-offs that influence system behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance Overhead&lt;/strong&gt;: Deduplication mechanisms, whether in the producer, broker, or consumer, introduce additional computational and storage costs. Checking for duplicates requires maintaining and querying caches or databases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scalability Limits&lt;/strong&gt;: High-throughput systems, especially those with high-cardinality workloads, can overwhelm deduplication caches. Memory usage increases with message volume, requiring careful TTL tuning or eviction policies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TTL Constraints&lt;/strong&gt;: Deduplication caches often operate within fixed time windows. Messages arriving after their identifiers have been evicted from the cache may be treated as new, reintroducing duplicates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;System Complexity&lt;/strong&gt;: Each layer involved in deduplication adds complexity. For example, producer-side deduplication requires state management, while consumer-side deduplication relies on external stores that need to scale with the system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;how-popular-systems-handle-deduplication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-popular-systems-handle-deduplication&quot; aria-label=&quot;how popular systems handle deduplication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;How Popular Systems Handle Deduplication&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Deduplication is a feature where the approach and depth vary significantly among messaging systems. Each system designs its handling of duplicates to balance scalability, fault tolerance, and throughput. Let’s explore how systems like &lt;strong&gt;RabbitMQ&lt;/strong&gt;, &lt;strong&gt;SQS&lt;/strong&gt;, &lt;strong&gt;Azure Service Bus&lt;/strong&gt; and &lt;strong&gt;Kafka&lt;/strong&gt; address deduplication, including the trade-offs inherent in their approaches.&lt;/p&gt;
&lt;p&gt;Let’s also try to modify &lt;em&gt;QueueBroker&lt;/em&gt; implementation to see how it’d change if we’d like to handle similar strategies.&lt;/p&gt;
&lt;h3 id=&quot;sqs-fifo-deduplication-with-message-grouping&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#sqs-fifo-deduplication-with-message-grouping&quot; aria-label=&quot;sqs fifo deduplication with message grouping permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;SQS FIFO: Deduplication with Message Grouping&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Amazon SQS FIFO offers deduplication through &lt;strong&gt;message group IDs&lt;/strong&gt;. Messages with the same group ID are deduplicated and processed in order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagededuplicationid-property.html&quot;&gt;SQS FIFO uses a deduplication ID derived from a user-provided ID or the message body.&lt;/a&gt;&lt;/strong&gt; If the same token is received within a deduplication window (up to five minutes), the message is ignored.&lt;/p&gt;
&lt;h5 id=&quot;trade-offs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#trade-offs&quot; aria-label=&quot;trade offs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Trade-Offs&lt;/strong&gt;&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Limited TTL&lt;/strong&gt;: SQS FIFO’s deduplication window is fixed. Messages outside the window are treated as new.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Throughput Limitations&lt;/strong&gt;: FIFO queues sacrifice throughput for strict ordering and deduplication.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How would our implementation look like if we tried to add deduplication capabilities? Let’s wrap our queue broker with additional code:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SQSDeduplicationQueueBroker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; deduplicationCache&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DeduplicationCache&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; broker&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueBroker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    deduplicationWindowMs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deduplicationCache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DeduplicationCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationWindowMs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    task&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueTask&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueTaskOptions &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; deduplicationId&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;broker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; deduplicationId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;deduplicationId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;deduplicationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deduplicationCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deduplicationCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        processesAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Our queue broker ensures that tasks are run sequentially, so we wrap task processing with a deduplication code. We check if the Deduplication cache has the ID and return the cached result if it does. Otherwise, we run the task and cache it.&lt;/p&gt;
&lt;p&gt;The dummy duplication cache implementation can look as follows (warning: it’s not thread-safe, but as we have sequential processing within a message group, then it should work fine).&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DeduplicationCache&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; deduplicationCache&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; object&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; cachedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; deduplicationWindowMs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationWindowMs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deduplicationWindowMs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; deduplicationWindowMs&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deduplicationCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deduplicationCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; object&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deduplicationCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;deduplicationId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      cachedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// This should be run periodically&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cleanupCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; toDelete &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deduplicationCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;entries&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cachedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; processesAt &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
          currentTime &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; processesAt &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deduplicationWindowMs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; id &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; toDelete&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;deduplicationCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course, in the messaging systems, we wouldn’t be caching the processing result, but just message IDs. We’re doing here an in-memory task processing, so that’s a bit different case. We’ll discuss in further editions how to make a real message out of it.&lt;/p&gt;
&lt;h4 id=&quot;rabbitmq-memory-based-deduplication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#rabbitmq-memory-based-deduplication&quot; aria-label=&quot;rabbitmq memory based deduplication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;RabbitMQ: Memory-Based Deduplication&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;RabbitMQ does not natively provide built-in deduplication for its traditional queues, but deduplication can be achieved using the &lt;a href=&quot;https://github.com/noxdafox/rabbitmq-message-deduplication&quot;&gt;RabbitMQ Message Deduplication Plugin&lt;/a&gt; or &lt;a href=&quot;https://www.rabbitmq.com/blog/2021/07/28/rabbitmq-streams-message-deduplication&quot;&gt;RabbitMQ Streams&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;rabbitmq-deduplication-plugin&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#rabbitmq-deduplication-plugin&quot; aria-label=&quot;rabbitmq deduplication plugin permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RabbitMQ Deduplication plugin&lt;/h3&gt;
&lt;p&gt;The RabbitMQ Message Deduplication Plugin is an add-on that enables deduplication for exchanges or queues based on message IDs. This plugin can keep message IDs in an in-memory cache or store them on a disk. They will be kept for a configurable time-to-live (TTL). Messages with duplicate IDs are discarded during the deduplication window.&lt;/p&gt;
&lt;h4 id=&quot;how-it-works&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-it-works&quot; aria-label=&quot;how it works permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Producers must include a unique message ID with each message header.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RabbitMQ maintains an in-memory cache of recently seen IDs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When a duplicate ID is detected, the message is ignored.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach is simple and effective for short-lived workloads, such as task queues, but it has limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TTL Constraints&lt;/strong&gt;: If a duplicate arrives after its ID has been evicted from the cache, RabbitMQ treats it as new.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Memory Overhead&lt;/strong&gt;: Storing message IDs for high-cardinality workloads (e.g., IoT systems with thousands of devices) consumes significant memory.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Persistent Tracking&lt;/strong&gt;: If RabbitMQ restarts, the deduplication cache is cleared, allowing duplicates to slip through.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The potential use case could be a batch processing system. It could use RabbitMQ to manage tasks submitted by multiple sources. Each task includes a unique ID. Deduplication ensures that retries due to source failures don’t lead to duplicate task execution.&lt;/p&gt;
&lt;p&gt;RabbitMQ’s deduplication plugin is lightweight but limited. It’s effective for workloads where duplicates are rare or occur within short time windows. However, such caching can become a bottleneck for long-lived or large-scale workloads.&lt;/p&gt;
&lt;h3 id=&quot;rabbitmq-streams-deduplication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#rabbitmq-streams-deduplication&quot; aria-label=&quot;rabbitmq streams deduplication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RabbitMQ Streams deduplication&lt;/h3&gt;
&lt;p&gt;RabbitMQ Streams, introduced in newer RabbitMQ versions, were added to keep up the competition with streaming solutions like Kafka, Pulsar, etc. &lt;a href=&quot;https://www.rabbitmq.com/blog/2021/07/28/rabbitmq-streams-message-deduplication&quot;&gt;It is designed for high-throughput, durable message streaming and includes native support for deduplication&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;how-it-works-1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-it-works-1&quot; aria-label=&quot;how it works 1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;How It Works&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Producers send messages with a unique &lt;code class=&quot;language-text&quot;&gt;publish-id&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RabbitMQ Streams use the &lt;code class=&quot;language-text&quot;&gt;publish-id&lt;/code&gt; to detect and discard duplicates at the broker level.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It persists in deduplication state durable. Even after a broker restart, the system retains knowledge of processed &lt;code class=&quot;language-text&quot;&gt;publish-ids&lt;/code&gt;, ensuring robust deduplication. Deduplication is tailored for continuous event processing, such as IoT telemetry or log aggregation.&lt;/p&gt;
&lt;h4 id=&quot;streams-vs-plugin&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#streams-vs-plugin&quot; aria-label=&quot;streams vs plugin permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Streams vs Plugin&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;The plugin can also store duplication cache, but it’s recommended to be used in memory. Deduplication state persists across restarts, eliminating the risk of processing duplicates after a failure. Streams are designed for large-scale workloads, with built-in support for high throughput and partitioning.&lt;/p&gt;
&lt;p&gt;RabbitMQ Streams is not compatible with traditional queues. It requires clients to support the streaming protocol. Streams are more suited for event-driven systems than for task-based queuing.&lt;/p&gt;
&lt;h4 id=&quot;queue-broker-implementation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#queue-broker-implementation&quot; aria-label=&quot;queue broker implementation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Queue Broker implementation&lt;/h4&gt;
&lt;p&gt;Still, logically, those two implementations will be similar to what you saw in the SQS query broker wrapper. Streams would keep the id as part of task without the need for TTL.&lt;/p&gt;
&lt;h3 id=&quot;azure-service-bus-session-based-deduplication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#azure-service-bus-session-based-deduplication&quot; aria-label=&quot;azure service bus session based deduplication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Azure Service Bus: Session-Based Deduplication&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Azure Service Bus sessions are a feature that allows grouping and ordered processing of related messages.&lt;/strong&gt; Sessions provide a stateful and sequential processing mechanism for scenarios where multiple messages are related to the same context (e.g., a specific customer, device, or order). This feature is handy for ensuring strict ordering and exclusive handling of messages for a given context.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Azure Service Bus session deduplication works logically close to SQS deduplication, but it’s much more sophisticated:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Duplicate Detection&lt;/strong&gt;: Azure Service Bus tracks message IDs in a deduplication store seen during a configured time window (default: 10 minutes). If a message with the same MessageId is received within this window, it is acknowledged as successfully received but discarded from further processing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TTL Flexibility&lt;/strong&gt;: The deduplication window can be configured, allowing for long-lived message deduplication. The deduplication store retains MessageIds for a configurable window ranging from 20 seconds to 7 days.&lt;/p&gt;
&lt;p&gt;A smaller window reduces memory overhead and improves throughput.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sessions&lt;/strong&gt;: When sessions are enabled, deduplication occurs within the context of the SessionId.When partitioning is enabled, deduplication uses the combination of MessageId and PartitionKey.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;What would our QueueBroker implementation look like following Azure Service Bus implementation?&lt;/strong&gt; There you have it!&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AzureServiceBusDedublicationQueueBroker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; sessionCache&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SessionCache&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; broker&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueBroker&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    sessionCleanupIntervalMs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionCache &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SessionCache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionCleanupIntervalMs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    task&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueTask&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueTaskOptions &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sessionId&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; messageId&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;broker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; messageId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; messageId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Skip deduplication if no sessionId&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; messageId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; messageId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionCache&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; messageId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The session cache will look as follow:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SessionCache&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; sessionData&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; messageIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; object&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; lastCleanup&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; sessionCleanupIntervalMs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionCleanupIntervalMs&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionCleanupIntervalMs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sessionCleanupIntervalMs&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hasMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; messageId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;messageIds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;messageId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; messageId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; object&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        messageIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        lastCleanup&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;messageIds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;messageId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;cleanupSession&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currentTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentTime &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastCleanup &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionCleanupIntervalMs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;messageIds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lastCleanup &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’s not that far from our RabbitMQ-like implementation, just adding a second level of caching. So, see, there is a pattern here!&lt;/p&gt;
&lt;h3 id=&quot;kafka-delegating-deduplication-to-consumers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#kafka-delegating-deduplication-to-consumers&quot; aria-label=&quot;kafka delegating deduplication to consumers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Kafka: Delegating Deduplication to Consumers&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Kafka, the high-throughput event streaming platform, takes a minimalist approach to deduplication. Its design prioritizes scalability and performance over broker-side message tracking, which means deduplication is left to the consumers.&lt;/p&gt;
&lt;h4 id=&quot;offsets-as-a-control-mechanism&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#offsets-as-a-control-mechanism&quot; aria-label=&quot;offsets as a control mechanism permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Offsets as a Control Mechanism&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Kafka relies on offsets to achieve idempotent processing. Every Kafka partition processes messages sequentially, and each consumer tracks its offset—the position of the last message successfully processed. When a consumer restarts, it resumes processing from the last stored offset.&lt;/p&gt;
&lt;p&gt;Offsets serve as a lightweight mechanism to prevent reprocessing, but they don’t address all cases of duplication:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Duplicates occur if a message is replayed intentionally (e.g., by resetting offsets for debugging or analytics).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If a consumer crashes before committing an updated offset, Kafka may redeliver the last processed message.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;idempotent-producers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#idempotent-producers&quot; aria-label=&quot;idempotent producers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Idempotent Producers&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;For critical use cases, Kafka provides &lt;strong&gt;idempotent producer&lt;/strong&gt; functionality. When enabled, Kafka ensures each message sent by a producer to a broker is assigned a unique sequence number. This prevents duplicates caused by retries during producer-broker communication. However, idempotent producers come with restrictions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;They require unique producer IDs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They only work within the scope of a single producer session, limiting cross-session guarantees.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kafka can’t do broker-based deduplication, as it batches messages into records. Then, it just passes the whole batch between the producer, broker and consumer. It doesn’t deserialise them or do any additional processing until it’s got in the consumer client. That’s why some people say that Kafka is not a real messaging system. Essentially, it &lt;em&gt;just&lt;/em&gt; shovels data from one place to another.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kafka’s approach offloads complexity to consumers.&lt;/strong&gt; This design achieves scalability, but developers must implement their own deduplication or idempotency at the consumer level. For high-throughput systems, this trade-off can be fine.&lt;/p&gt;
&lt;p&gt;The consumer-based deduplication, you could use techniques like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/idempotent_command_handling/#handling-duplicates&quot;&gt;Business logic check&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/outbox_inbox_patterns_and_delivery_guarantees_explained/&quot;&gt;Inbox pattern&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/idempotent_command_handling/#generic-idempotency-handling&quot;&gt;Idempotent store&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;exactly-once-delivery-vs-exactly-once-processing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#exactly-once-delivery-vs-exactly-once-processing&quot; aria-label=&quot;exactly once delivery vs exactly once processing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Exactly-Once Delivery vs. Exactly-Once Processing&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The concept of exactly-once delivery—the idea that a message is delivered to a consumer only once—is a broken promise of messaging tools marketing. Failures, retries, and network partitions make it impossible to guarantee that a message won’t be delivered multiple times. Instead, the focus should shift to &lt;strong&gt;exactly-once processing&lt;/strong&gt;, which ensures that the effects of processing a message are applied only once.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As discussed earlier, failure can happen at each part of the delivery pipeline, timeouts may occur, and retires may be needed.&lt;/strong&gt; The producer retries if the broker receives the message but loses the acknowledgement. The broker now has two copies of the same message. If a broker crashes after delivering a message but before recording the delivery, it may redeliver the message upon recovery. If the consumer crashes or just times out and doesn’t accept the message delivery, the broker will also retry.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://towardsdatascience.com/cap-theorem-and-distributed-database-management-systems-5c2be977950e&quot;&gt;The CAP theorem (Consistency, Availability, Partition Tolerance)&lt;/a&gt; highlights the trade-offs: distributed systems must tolerate failures but can’t simultaneously guarantee strict consistency. Exactly-once delivery sacrifices availability, which is often unacceptable in real-world systems.&lt;/p&gt;
&lt;h3 id=&quot;achieving-exactly-once-processing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#achieving-exactly-once-processing&quot; aria-label=&quot;achieving exactly once processing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Achieving Exactly-Once Processing&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Exactly-once processing acknowledges that while duplicates may occur, their effects should only be applied once. Achieving this requires combining deduplication with idempotency and transactional guarantees.&lt;/p&gt;
&lt;h4 id=&quot;idempotency&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#idempotency&quot; aria-label=&quot;idempotency permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Idempotency&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Consumers must ensure that repeated processing of the same message produces the same result. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A payment system checks a transaction ID against a database before applying charges.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A state machine avoids re-executing a completed step by checking its current state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;transactional-outbox&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#transactional-outbox&quot; aria-label=&quot;transactional outbox permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Transactional Outbox&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;The &lt;strong&gt;transactional outbox pattern&lt;/strong&gt; ensures atomicity between database operations and message publishing. For example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;A payment service writes a transaction record to an outbox table in the same database transaction as updating the user’s balance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A background process reads the outbox and publishes messages to a broker.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This pattern eliminates inconsistencies caused by partial updates or message delivery failures.&lt;/p&gt;
&lt;h4 id=&quot;distributed-locking&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#distributed-locking&quot; aria-label=&quot;distributed locking permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Distributed Locking&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Distributed locks can enforce mutual exclusion, ensuring that only one instance of a consumer processes a given message. While effective for strict sequencing, locks add latency and require careful handling to avoid deadlocks.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Deduplication is critical in distributed systems, but it’s only one part of the solution. It’s always a best-effort. It can decrease the scale of duplicate message delivery, but it may also happen.&lt;/p&gt;
&lt;p&gt;By understanding the trade-offs of technologies like Kafka, RabbitMQ, and Azure Service Bus and combining deduplication with patterns like idempotency and transactional outboxes, architects can design systems that achieve exactly-once processing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You should always think about making your consumer idempotent to handle message processing correctly.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Ordering, Grouping and Consistency in Messaging systems]]></title><description><![CDATA[We went quite far from our Queue Broker series in recent editions, but today, we’re back to it! Did you miss it? In an initial article, we…]]></description><link>https://event-driven.io/en/ordering-grouping-and-consistency/</link><guid isPermaLink="false">https://event-driven.io/en/ordering-grouping-and-consistency/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;We went quite far from our Queue Broker series in recent editions, but today, we’re back to it!&lt;/strong&gt; Did you miss it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;In an initial article, we introduced the QueueBroker&lt;/a&gt;—a central component that manages queuing, backpressure, and concurrency.&lt;/strong&gt; In distributed systems, queues do more than buffer requests or smooth traffic spikes. They can ensure order, enforce grouping, and provide guarantees for exclusive processing. These properties are critical when designing systems that must be both scalable and consistent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;/en/idempotent_command_handling/&quot;&gt;On Friday, I wrote an extensive guide on how to handle idempotency in command handling on my blog.&lt;/a&gt;&lt;/strong&gt; I explained the challenges in ensuring that our business operations are handled correctly without duplicates. I went through the various ways to handle it, both explicitly and generically, from building predictable state machines to handling it in business logic to leveraging Optimistic Concurrency, retries, and distributed locks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I recommend handling idempotency using business logic, as it’s highly dependent on the business rules. And those rules tend to change.&lt;/strong&gt; If we use generic handling, we’re always adding additional overhead, even if most of our applications rarely have idempotency issues. That’s something to consider, as it can also increase costs in cloud environments.&lt;/p&gt;
&lt;p&gt;Such implementations are also tricky. We need to handle thread safety and atomic updates correctly. I showed the implementation using a distributed lock like Redis, as in-memory implementation isn’t that simple.&lt;/p&gt;
&lt;p&gt;And &lt;em&gt;“in-memory implementation isn’t that simple”&lt;/em&gt; is a good challenge. And I’ll take it today!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s a good opportunity to merge ideas from those two articles. We’ll build a thread-safe, in-memory&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;IdempotencyKey Store&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;by combining single-writer, grouped queuing with idempotency guarantees.&lt;/strong&gt; We’ll also explore the broader architectural principles behind grouping and ordering in queuing systems like Amazon SQS FIFO, Kafka, and Azure Service Bus.&lt;/p&gt;
&lt;p&gt;But first, let’s start with the problem grouping solves.&lt;/p&gt;
&lt;h2 id=&quot;why-grouping-matters&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-grouping-matters&quot; aria-label=&quot;why grouping matters permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Grouping Matters&lt;/h2&gt;
&lt;p&gt;In distributed systems, operations in a shared state—whether it’s a bank account, an order, or a device—are often processed asynchronously. But asynchronous processing introduces risk:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Transactions arrive out of order:&lt;/strong&gt; Imagine a bank processes a withdrawal before a deposit. A user with a $0 balance may see a failed transaction, even though funds were deposited seconds earlier.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Duplicate messages cause inconsistent state:&lt;/strong&gt; A hotel system receives two commands to check in a guest. Without safeguards, the system may allocate two rooms to the same guest.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Concurrency can break assumptions:&lt;/strong&gt; In an IoT system, a device sends a “door open” event while another service processes a “door locked” command. Without guarantees, these commands could execute simultaneously, leaving the system in an undefined state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Grouping allows related operations to be processed sequentially while operations on different groups run concurrently.&lt;/strong&gt; We’re correlating a set of tasks, telling that they need to be processed in the certain order: For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Banking systems group by account:&lt;/strong&gt; Ensuring withdrawals and deposits for the same account are serialized.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Order management systems group by order:&lt;/strong&gt; Ensuring updates to the same order (e.g., payment, shipping) are processed in order.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IoT systems group by device:&lt;/strong&gt; Ensuring events from the same device don’t conflict.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Grouping isn’t a new idea. Systems like Amazon SQS FIFO, Azure Service Bus, and Kafka have implemented variations of this pattern to solve similar problems.&lt;/p&gt;
&lt;h2 id=&quot;how-popular-systems-handle-grouping-and-ordering&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-popular-systems-handle-grouping-and-ordering&quot; aria-label=&quot;how popular systems handle grouping and ordering permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How Popular Systems Handle Grouping and Ordering&lt;/h2&gt;
&lt;h3 id=&quot;amazon-sqs-fifo&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#amazon-sqs-fifo&quot; aria-label=&quot;amazon sqs fifo permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Amazon SQS FIFO&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Regular &lt;a href=&quot;https://aws.amazon.com/sqs/&quot;&gt;Amazon SQS&lt;/a&gt; doesn’t provide any ordering guarantee. Still, some years ago, AWS added a variation: &lt;a href=&quot;https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-fifo-queues.html&quot;&gt;Amazon SQS FIFO (First-In-First-Out)&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Amazon SQS FIFO ensures messages with the same message group ID are processed in the right order. Groups are independent, so tasks for one group don’t block tasks in another. For example, in an e-commerce system, group messages by &lt;em&gt;orderId&lt;/em&gt; to ensure updates for the same order (e.g. &lt;em&gt;“Order Placed”,&lt;/em&gt; &lt;em&gt;“Payment Confirmed”&lt;/em&gt;) are processed sequentially. Messages for different orders can be processed concurrently.&lt;/p&gt;
&lt;p&gt;Of course, ordering guarantees come at the cost of throughput: FIFO queues process fewer messages per second than standard queues. We may be also facing bottlenecks in group processing. A slow task in one group delays all subsequent tasks in that group.&lt;/p&gt;
&lt;h3 id=&quot;azure-service-bus&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#azure-service-bus&quot; aria-label=&quot;azure service bus permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Azure Service Bus&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/service-bus-messaging/message-sessions&quot;&gt;Azure Service Bus uses sessions&lt;/a&gt; to group related messages. Messages in a session are processed sequentially, similar to SQS FIFO, but with added features like session locks to ensure only one consumer handles a session at a time.&lt;/p&gt;
&lt;p&gt;Session locks make Azure Service Bus robust against failures, but managing them adds complexity. Throughput can be impacted if too many messages are grouped into the same session.&lt;/p&gt;
&lt;h3 id=&quot;kafka&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#kafka&quot; aria-label=&quot;kafka permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Kafka&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Kafka achieves grouping through partitioning.&lt;/strong&gt; Each partition processes events sequentially, and partitions can be assigned to consumers for parallel processing. Partitioning requires careful design.&lt;/p&gt;
&lt;p&gt;Partitions represent a physical split. Poor partitioning can lead to uneven workload distribution. You can also have a single consumer inside the consumer group to handle those messages, but you cannot distribute them. When consumers fail, or new consumers join, partitions must be reassigned, temporarily disrupting processing.&lt;/p&gt;
&lt;h3 id=&quot;rabbitmq&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#rabbitmq&quot; aria-label=&quot;rabbitmq permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;RabbitMQ&lt;/h3&gt;
&lt;p&gt;RabbitMQ ensures message ordering within a queue. Messages are delivered in the same order they are enqueued. However, once multiple consumers subscribe to the queue, RabbitMQ balances messages across consumers in a round-robin fashion, potentially disrupting the ordering.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RabbitMQ doesn’t natively support grouping like SQS FIFO or Azure Service Bus sessions.&lt;/strong&gt; Implementing message grouping typically requires creating additional queues or custom routing logic with exchanges. To achieve that you could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use one queue per group. For instance, in an e-commerce system, you can create a queue for each orderId to process updates like “Order Placed” and “Order Shipped” sequentially.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Alternatively, use consistent hashing to route messages with the same key (e.g., orderId) to the same queue, ensuring grouped ordering.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;While creating separate queues for each group ensures strict ordering, it increases operational complexity.&lt;/strong&gt; It can lead to scaling issues. Managing thousands of queues for a high-cardinality grouping key like userId can overwhelm RabbitMQ. It also causes increased resource usage. Each queue consumes memory and CPU, leading to overhead in large-scale systems.&lt;/p&gt;
&lt;h2 id=&quot;guaranteeing-idempotency-with-queue&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#guaranteeing-idempotency-with-queue&quot; aria-label=&quot;guaranteeing idempotency with queue permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Guaranteeing idempotency with queue&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;/en/idempotent_command_handling/&quot;&gt;The generic implementation of the idempotency handling looks as follows&lt;/a&gt;. We:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Attempt to acquire the lock for the specified key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the attempt wasn’t successful, return the default result.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If we acquire the lock, then run the business logic.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If it fails, release the lock. We don’t want to lock the key, let someone fix the state condition, and retry later.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If it was successful, accept the lock and return the result.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In code it can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;idempotent&lt;/span&gt; &lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IdempotentOptions&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeoutInMs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; defaultResult&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; store &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;//Attempt to acquire the lock for the specified key&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appended &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; timeoutInMs &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// If it was used, return the default result&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;appended&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defaultResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// run handler&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Mark operation as successful&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Release key if it was used already&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IdempotentOptions&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  store&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IdempotencyKeyStore&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;defaultResult&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  timeoutInMs&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The store can be defined as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IdempotencyKeyStore&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; timeoutInMs&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The store has the following methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;tryLock&lt;/em&gt; - that tries to lock the specified key for a certain period of time. Returns true if the lock was acquired and false otherwise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;accept&lt;/em&gt; - accepts the key lock after successful handling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;release&lt;/em&gt; - releases the lock in case of handling failure.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What’s most important is that those operations should be thread-safe and atomic. Without that, we may face race conditions. The safest option is to use a distributed lock, e.g., Redis or a relational database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But we’re not here today to choose the safest options but to learn!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What if we had an implementation that ensures that we schedule the processing of a certain task one at a time? &lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;Actually, we have it from our older edition&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let’s join those forces together!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/fde45872ff80e9b2ec40cfca732646f5/6c738/2024-11-18-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMEAv/EABYBAQEBAAAAAAAAAAAAAAAAAAMBAv/aAAwDAQACEAMQAAABkYZSzDTZ/wD/xAAZEAADAQEBAAAAAAAAAAAAAAABAhEAITH/2gAIAQEAAQUCRQQBc47Ipz+//8QAFhEBAQEAAAAAAAAAAAAAAAAAADFR/9oACAEDAQE/AcR//8QAFxEBAQEBAAAAAAAAAAAAAAAAEQASIf/aAAgBAgEBPwHSy9v/xAAaEAACAgMAAAAAAAAAAAAAAAAQEQACMUFh/9oACAEBAAY/AuqawhU//8QAHBABAAIBBQAAAAAAAAAAAAAAAQARITFBcYHw/9oACAEBAAE/IVAasJTVKUikcQCDCwUnZMz7ef/aAAwDAQACAAMAAAAQk+//xAAXEQEBAQEAAAAAAAAAAAAAAAABACFR/9oACAEDAQE/EAxdnSX/xAAYEQACAwAAAAAAAAAAAAAAAAAAARFBkf/aAAgBAgEBPxCTNSNB/8QAGxABAQADAQEBAAAAAAAAAAAAAREAIUGxofD/2gAIAQEAAT8QMgZetzHJ2gDoV8wBH5XArgFRi6cBBbbXggfM3DtR9Z//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/fde45872ff80e9b2ec40cfca732646f5/507e6/2024-11-18-cover.webp 200w,
/static/fde45872ff80e9b2ec40cfca732646f5/28a80/2024-11-18-cover.webp 400w,
/static/fde45872ff80e9b2ec40cfca732646f5/8d2ea/2024-11-18-cover.webp 800w,
/static/fde45872ff80e9b2ec40cfca732646f5/68fc1/2024-11-18-cover.webp 1200w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/fde45872ff80e9b2ec40cfca732646f5/37402/2024-11-18-cover.jpg 200w,
/static/fde45872ff80e9b2ec40cfca732646f5/4cda9/2024-11-18-cover.jpg 400w,
/static/fde45872ff80e9b2ec40cfca732646f5/c60e9/2024-11-18-cover.jpg 800w,
/static/fde45872ff80e9b2ec40cfca732646f5/6c738/2024-11-18-cover.jpg 1200w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/fde45872ff80e9b2ec40cfca732646f5/c60e9/2024-11-18-cover.jpg&quot;
            alt=&quot;Captain Planet and the Planeteers imagined a kinder world. What can we do  better? | SYFY WIRE&quot;
            title=&quot;Captain Planet and the Planeteers imagined a kinder world. What can we do  better? | SYFY WIRE&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We’ll use a queue broker single-writer capabilities to ensure that only a single check for the existence of a specific idempotency key will be made in parallel.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our Queue Broker allows multiple tasks to be enqueued and handled asynchronously.&lt;/strong&gt; As long as we don’t reach the maximum number of active tasks, they can be processed in parallel. This is useful for limiting the number of connections in the connection pool.&lt;/p&gt;
&lt;p&gt;The other tasks will wait in the queue to be processed. Our Queue Broker uses a single-writer pattern to ensure that scheduling happens sequentially. We can enqueue multiple tasks, but they’re not processed immediately; the background queue processing processes them. Thanks to that, we can control the concurrency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s what we need today: limit the concurrency processing per task group.&lt;/strong&gt; Let’s adapt our initial implementation to handle those needs. Let’s start by adding tracking of the active task groups (&lt;em&gt;note: I’m using TypeScript here)&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Queue&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; QueueItem&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueueBroker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; queue&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Queue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; isProcessing &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; activeTasks &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; activeGroups&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Set&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;//(...)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re already keeping:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the tasks queue,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;semaphore informing if we’re currently processing tasks already,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;active tasks counter&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’re adding a simple set to keep the list of currently active task groups.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now, we need to update the enqueue method to allow specifying the task group id:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Queue&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; QueueItem&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueueTaskOptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; taskGroupId&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueueItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueTaskOptions&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueueBroker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (...)  &lt;/span&gt;

  &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;task&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueTask&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueTaskOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxQueueSize&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&apos;Too many pending connections. Please try again later.&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;task&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;schedule&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    task&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueTask&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueTaskOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;promiseWithDeadline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;taskWithContext&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolveTask&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; failTask&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; taskPromise &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              ack&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; resolveTask&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

            taskPromise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
              &lt;span class=&quot;token function&quot;&gt;failTask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
              &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; task&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; taskWithContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isProcessing&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ensureProcessing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; deadline&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxTaskIdleTime &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;We’re making it optional. If someone doesn’t provide the task group id, the task will be processed with the default behaviour (so it will not have any causal relation with other tasks).&lt;/strong&gt; In the schedule method, we’re also just passing the task together with options.&lt;/p&gt;
&lt;p&gt;The real code happens in the queue processing. Just to recap, only a single call of a &lt;em&gt;processQueue method&lt;/em&gt; can happen at a time (for details, &lt;a href=&quot;https://www.architecture-weekly.com/i/147613456/single-writer-pattern&quot;&gt;check the previous article&lt;/a&gt;).&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueueBroker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (...)  &lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;processQueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeTasks &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxActiveTasks &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; item &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;takeFirstAvailableItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// finish processing if there&apos;s nothing to process&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; groupId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;taskGroupId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;groupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token comment&quot;&gt;// Mark the group as active&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeGroups&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;groupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeTasks&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;executeItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isProcessing &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hasItemsToProcess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeTasks &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxActiveTasks
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ensureProcessing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, besides the concurrency check based on the currently active tasks, we’re also ensuring that there’s no other processing happening with the same group id.&lt;/p&gt;
&lt;p&gt;We achieve that by searching for the first eligible task using the &lt;em&gt;takeFirstAvailableItem&lt;/em&gt; method:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueueBroker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (...)  &lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; takeFirstAvailableItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueItem &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; taskIndex &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findIndex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;taskGroupId &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeGroups&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;taskGroupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;taskIndex &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// All remaining tasks are blocked by active groups&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Remove the task from the queue&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;splice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;taskIndex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; item&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; hasItemsToProcess &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queue&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findIndex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;taskGroupId &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeGroups&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;item&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;taskGroupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Eligible means, in this context, without task group id or task group id that’s not processed currently.&lt;/strong&gt; We return null and stop processing if an eligible item isn’t found. This means that we have exhausted the maximum number of active tasks, and all remaining are task group ids currently being processed. We can stop scheduling tasks, which will be rescheduled after task processing.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueueBroker&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (...)  &lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;executeItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; task&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueItem&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;finally&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeTasks&lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// Mark the group as inactive after task completion&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;taskGroupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeGroups&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;taskGroupId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// schedule main processing if it&apos;s not running&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ensureProcessing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Tasks with taskGroupIds that are currently active are skipped, but they remain in the queue and maintain their position.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If a task’s group is still marked as active due to an asynchronous delay in cleaning up active groups, that task will be skipped for this iteration. However, it remains in the queue and will be picked up in the next loop iteration once the group becomes inactive.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tasks continue to be processed as long as there are eligible tasks in the queue.&lt;/p&gt;
&lt;h3 id=&quot;example-walkthrough&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#example-walkthrough&quot; aria-label=&quot;example walkthrough permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Example Walkthrough&lt;/h3&gt;
&lt;p&gt;Let’s say that we enqueued the following tasks:&lt;/p&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;
&lt;p&gt;T1 - G1&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;T2 - G1&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;T3 - G2&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;T4 - G3&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;T5 -G1&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The example iteration can look as follow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;First Iteration:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;T1 (G1) is dequeued and processed.&lt;br&gt;
&lt;strong&gt;queue = [ T2, T3, T4, T5 ]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;activeGroups = [ G1 ]&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;T2 (G1) is skipped because G1 is active.&lt;br&gt;
&lt;strong&gt;queue = [ T2, T3, T4, T5 ]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;activeGroups = [ G1 ]&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;T3 (G2) is dequeued and processed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;queue = [ T2, T4, T5 ]&lt;br&gt;
activeGroups = [ G1, G2 ]&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Task 4 (group3) is dequeued and processed.&lt;br&gt;
&lt;strong&gt;queue = [ T2, T5 ]&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;activeGroups = [ G1, G2, G3 ].&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;T5 (G1) is skipped because G1 is active.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;queue = [ T2, T5 ]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;activeGroups = [ G1, G2, G3 ].&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Second Iteration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When T1 finishes, G1 is removed from activeGroups.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;queue = [ T2, T5 ]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;activeGroups = [ G2, G3 ].&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;T2 (G1) is now eligible and dequeued for processing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;queue = [ T5 ]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;activeGroups = [ G1, G2, G3 ].&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When T3 or T4 finishes, nothing happens, as only T5 is left, and it has G1, which is actively handled.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;queue = [ T5 ]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;activeGroups = [ G1 ].&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Third Iteration&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;When T2 finishes, G1 is removed from activeGroups.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;queue = [ T5 ]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;activeGroups = [ ].&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;T5 (G1) is now eligible and dequeued for processing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;queue = [ ]&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;activeGroups = [ G1 ].&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The fourth iteration is also the last one, as there are no more tasks to be processed.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Of course, the order is related to the time of task processing and maximum active tasks, but most importantly, group tasks are guaranteed to be processed sequentially.&lt;/p&gt;
&lt;h3 id=&quot;idempotency-key-store-with-queue&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#idempotency-key-store-with-queue&quot; aria-label=&quot;idempotency key store with queue permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Idempotency Key Store with Queue&lt;/h3&gt;
&lt;p&gt;Having that, we can use our Queue Broker to implement our Idempotency Key Store.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IdempotencyKeyStore&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; lockedKeys&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Map&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Locking&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Accepted&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IdempotencyKeyStoreOptions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lockedKeys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Key is already locked&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Use the QueueBroker to schedule a lock acquisition task&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queueBroker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ack &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hasKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lockedKeys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;hasKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lockedKeys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Locking&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;ack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hasKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// Ensure tasks for the same key are serialized&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; taskGroupId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; key &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Set the key to be accepted and marked as used&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lockedKeys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lockedKeys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Accepted&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Remove the key from the locked set, allowing new tasks to proceed&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lockedKeys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;has&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;lockedKeys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course, that’s just a basic implementation that doesn’t include time to live, etc. but this could be also implemented based on our (not so?) simple Queue Broker.&lt;/p&gt;
&lt;h2 id=&quot;trade-offs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#trade-offs&quot; aria-label=&quot;trade offs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Trade-Offs&lt;/h2&gt;
&lt;p&gt;Of course, our implementation has tradeoffs, so please, please don’t take it to production. What tradeoffs?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Linear Search Overhead:&lt;/strong&gt; Iterating through the queue to find the first task whose group is not active is linear (O(n)), which can add overhead if the queue grows large. While it’s simple and ensures FIFO order, frequent searches might degrade performance slightly as the queue grows. For very large queues we’d need to use different structures, maybe queue per key, but then we could fell down to the other issues we discussed on RabbitMQ handling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Delay in Processing Blocked Tasks:&lt;/strong&gt; Tasks belonging to active groups remain blocked in the queue until their group becomes inactive. This can lead to inefficiency if most tasks belong to a few heavily used groups, as tasks from other groups might have to wait unnecessarily while the queue is scanned.&lt;/p&gt;
&lt;p&gt;In our case, this delay should be minor since blocked tasks remain in the queue and are retried in the next loop iteration. But for the real world queue processing tasks may not be as simple as just checking in the key store cache. To optimize further, you could prioritize ungrouped or less-congested groups, but this could break FIFO guarantees.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Asynchronous Cleanup Lag:&lt;/strong&gt; If there’s a delay in cleaning up active groups (due to asynchronous task completion), eligible tasks might be skipped unnecessarily for one iteration. The delay is usually negligible and doesn’t break FIFO, but it might result in minor inefficiencies in high-throughput systems. The retry mechanism works well and ensures eventual processing in most cases. No additional mitigation is required unless you observe frequent long-running tasks or unexpected delays.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fairness with Ungrouped Tasks:&lt;/strong&gt; If many tasks have taskGroupId, ungrouped tasks might have to wait longer than expected due to the findIndex search order. The current approach does not explicitly prioritize ungrouped tasks, even though they are always eligible for execution. If fairness for ungrouped tasks is critical, you could maintain two separate queues (grouped and ungrouped) and process ungrouped tasks first. This would add complexity but ensure fairness.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h2&gt;
&lt;p&gt;As you see, most of those issues can also be transferred to the real queues we discussed and the real tradeoff analysis we could perform in our systems.&lt;/p&gt;
&lt;p&gt;I hope that this article has shown you the challenges and potential solutions to implementing message grouping. While it’s good to have, it can also degrade performance and scalability.&lt;/p&gt;
&lt;p&gt;We should be careful about that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What would you say if, in the next edition, I’d go with queue prioritisation or deduplication?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Also last but not least. How do you like the format? What would you change in it? What topics would you like to see here?&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Idempotent Command Handling]]></title><description><![CDATA[“Is your command handling idempotent?” Sounds like a douchebag question to ask. Actually, that’s not a question but a statement that we want…]]></description><link>https://event-driven.io/en/idempotent_command_handling/</link><guid isPermaLink="false">https://event-driven.io/en/idempotent_command_handling/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2018e411f2f9c31e7df9adf38f8405b6/b09c1/2024-11-15-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADM0lEQVR42mWS60/bZRTH28JKL6Mtvd8o0pYyoO0EGtuuBcIgurYwacsY1LYsFRlYbnETGCsxWIkL01WTzeniLdMg0cjU4BaJiYmXJRqTZe98Z+I/8vFngxq2Fyc5z5Pv83nO95wjEolEtDR3cDaZY6qwRCI+QSR8kqfjSWYulrkgxHD/IG6jnb7OCJOpSaazRbKCPtjeXb03qzT8wzkIETJJLdajWoJODz6bFYdKhVZaS5NOj8fRjMtsJ+DxEWjrwqQ1olZqsOkt9HaGiPhDmFQNh4E1IokAa6H3WCvhFjejwSiJ450kAwHiTwYwqnV4G53opXUoxWJqhTdyoQiFcA75n8Khtx4GqmVyuhrt5EN+rmROU5mboTI7QzmXZywYxinYcumM9LbYGGh30WHS47ObsSilqOrkNBksh4FOXQPPtLq4NhanMp5iv/IqD3c/Zf/aFbbyOY4Ln51wGPlweoA7pXHubk7ycTHGkNOAXCzBon7Ess9i4vKpPu6tL/DTW2+wV5rn55tvs/vyCl+slzjTG6Q8GuTeRpbb00Psb+RYPRVgNR6mQ6/CWK8+DHTrdWwO9fPNcoH7119ne+45Hnz2EX99t8dWcZZmnYGdxRi/vzvLH59f5v6NIpcG/Hw5HyPaaMCkfmTKyto6VgbDfPL8MHfXpvhqbZ7fbt/iz70dCokY9VIlpXSUHzbTPHh/lt2VEQpeK3mPFo9G9bjlI8LEitFuwU6Sry/k2Vta4OF719neWEYjVwoVaPHandw4P8DOVITt8TClkJPuBhkeg/HxPWzSqFkbjHD1zCDfl5f49Z0tfrl5leAxT1Vk1+loszooZ3vYfiHKB+ku5rxm2tVyHA0GoYeq/4FiSQ2Go3rcqnr6XU3MnTzBt+urvJROHogkqBUGnOYQIXcbb2aijDgttKoV1X20Gmx4nmg70IoFoFhEX/8igUAGzRElhZSUW8sBxmMGhhJiesIiUikXiwtjjCTkbMzruPOKhx6zsgoxmyyEg6lqLv7Xcnr0NS5e+pHMRIVzGR/ZCQunh0OcTSs4l60h9ayJdLKDF8+byOVdJHpkdDtcKGT1tDq92G3+/yz/DXihtQ2WUqOOAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/2018e411f2f9c31e7df9adf38f8405b6/507e6/2024-11-15-cover.webp 200w,
/static/2018e411f2f9c31e7df9adf38f8405b6/28a80/2024-11-15-cover.webp 400w,
/static/2018e411f2f9c31e7df9adf38f8405b6/0367d/2024-11-15-cover.webp 626w&quot;
              sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/2018e411f2f9c31e7df9adf38f8405b6/36ca5/2024-11-15-cover.png 200w,
/static/2018e411f2f9c31e7df9adf38f8405b6/a3397/2024-11-15-cover.png 400w,
/static/2018e411f2f9c31e7df9adf38f8405b6/b09c1/2024-11-15-cover.png 626w&quot;
            sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/2018e411f2f9c31e7df9adf38f8405b6/b09c1/2024-11-15-cover.png&quot;
            alt=&quot;2024 11 15 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“Is your command handling idempotent?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sounds like a douchebag question to ask. Actually, that’s not a question but a statement that we want to sound smart.&lt;/p&gt;
&lt;p&gt;Maybe a definition from Wikipedia could help?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Idempotence (UK: /ˌɪdɛmˈpoʊtəns/, US: /ˈaɪdəm-/) is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Meh, even more douchy.&lt;/p&gt;
&lt;p&gt;Yet, if we frame it as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“How do you handle business logic that should only be run once when the record doesn’t exist? For instance: creating a user on the first call or ensuring that we’re checking in guests to the hotel only once.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Should checking out a guest fail or succeed if the guest was already checked out?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now we’re talking! We’re bringing the discussion back. We can reason and discuss specific cases.&lt;/p&gt;
&lt;p&gt;Let’s say that we have the Guest Stay finances workflow explained in the &lt;a href=&quot;/en/how_to_have_fun_with_typescript_and_workflow/&quot;&gt;How TypeScript can help in modelling business workflows&lt;/a&gt;. It starts by checking in guests; then we record charges (e.g. night stay, beer at the bar, massage at SPA) and pay for them. Guests can check out if our balance is settled (so the difference between charges and payments equals zero).&lt;/p&gt;
&lt;p&gt;Now, we could start with the naive implementation that throws exceptions when the business rule is not fulfilled. For check-in, this could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkIn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckIn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestCheckedIn &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; guestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; roomId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metadata &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Guest is already checked-in!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Guest account is already checked out&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; metadata&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckedIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      guestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      roomId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      guestStayAccountId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toGuestStayAccountId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;guestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; roomId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      checkedInAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I designed my model to express possible states. The guest state can be either not existing, checked in or checked out. Essentially it’s a state machine.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotExisting&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotExisting&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CheckedIn&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; balance&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CheckedOut&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedOut&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GuestStayAccount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; NotExisting &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; CheckedIn &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; CheckedOut&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; initialState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccount &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotExisting&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Thanks to that, I can make the explicit check, ensuring that I assume I can perform the check-in only when the guest’s stay does not exist. Otherwise, I’m throwing exceptions with meaningful messages.&lt;/p&gt;
&lt;p&gt;That could work for the basic scenario. &lt;strong&gt;Yet, even if we’re designing the business logic or backend code, we should always consider user experience.&lt;/strong&gt; Throwing exceptions helps us ensure that we won’t end up in the wrong state, which is a minimum effort we should make. Yet, undoubtedly, it doesn’t provide a good user experience. Some can say:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No worries, we’ll return the Result instead of throwing an exception!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s sweet, but for me, it’s Potato vs Potahto.&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/Mc3Fn7R9mkE?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Of course, I understand the principles of not driving our logic by exceptions, but we can map exceptions and results to specific HTTP statuses, pop-ups with an error message, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Whether we throw exceptions or return &lt;em&gt;Result.Error&lt;/em&gt;, it’ll be the same (bad) user experience.&lt;/strong&gt; The flow is stopped with an error. It’s “just” syntactic sugar we prefer.&lt;/p&gt;
&lt;p&gt;If we haven’t done that yet, it’s about time to ask a business expert what should happen if someone tries to check in twice. If you think that this can’t ever happen, then think about scenarios like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the hotel clerk clicks check-in twice,&lt;/li&gt;
&lt;li&gt;someone uses the check-in booth,&lt;/li&gt;
&lt;li&gt;we have a quick check-in workflow, where automatically, a guest account is created, a confirmed reservation, and checked in,&lt;/li&gt;
&lt;li&gt;we’re integrating with external systems that can send us a web-hook multiple times,&lt;/li&gt;
&lt;li&gt;the process is asynchronous or initiated in other modules, and queues can retry command delivery.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It may appear that it’d be a better experience to succeed silently if the check-in has already been made. So, we accept the command and say, “Yeah, fine.”  How to do it?&lt;/p&gt;
&lt;p&gt;The easiest option is to just remove if statement:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Guest is already checked-in!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Still, if we just do that, then business logic would go forward, and it’d return &lt;em&gt;GuestCheckedIn&lt;/em&gt; again. It’d be stored, and that’s not necessarily where we’d like to end up. We’ve already checked in, and we don’t care how many more times someone tries to do it again. What other options do we have, then?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We should have a way to say that we’re ignoring the command.&lt;/strong&gt; There are multiple options to achieve it, for instance, by returning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;null,&lt;/li&gt;
&lt;li&gt;result type informing that we’re ignoring processing,&lt;/li&gt;
&lt;li&gt;explicit event telling that error condition was achieved,&lt;/li&gt;
&lt;li&gt;empty array or events,&lt;/li&gt;
&lt;li&gt;another creative way you came up with.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Again, it’s a bit potato vs potahto discussion, highly dependent on your preferences. Let me go through mine. Returning null, in this case, could be meaningless for other people unless that’s our general convention. Returning explicit events is fair but sometimes a bit too heavy; we’ll discuss that later in more detail. Result type informing that we’re ignoring processing would be best if we’re not doing Event Sourcing, as we need to know whether we should update the state with the new one. &lt;strong&gt;As I’m doing Event Sourcing here, my safe default would be an empty array of events. In Event Sourcing, logic should return one or an array of events, so this should not introduce additional complexity.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;An updated method will look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;checkIn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; guestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; roomId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metadata &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckIn&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestCheckedIn &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Guest account is already checked out&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; now &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; metadata&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckedIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      guestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      roomId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      guestStayAccountId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toGuestStayAccountId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;guestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; roomId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      checkedInAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A simple change, but it makes our processing more explicit. If we prefer, we can still throw an exception if the guest was checked out. That can mean something is fishy, and we may still want to throw an exception in this case. Still, we should always discuss that with business. It’s fine to mix multiple strategies for various invariants.&lt;/p&gt;
&lt;h2 id=&quot;command-handling-infrastructure&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#command-handling-infrastructure&quot; aria-label=&quot;command handling infrastructure permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Command Handling infrastructure&lt;/h2&gt;
&lt;p&gt;Such change, of course, has to be reflected in our infrastructure. In Event Sourcing the flow looks as follow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the stream events and build the state from them.&lt;/li&gt;
&lt;li&gt;Run the business logic based on the state and passed command.&lt;/li&gt;
&lt;li&gt;Store new events in the same stream you’ve read.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, between 2 and 3, we should add an additional step checking if any changes have been made, so in our case, an empty array was returned. The pseudo-code for a generic command handling was returned could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 1. Aggregate the stream&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; aggregationResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; eventStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;aggregateStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Use the aggregate state&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; aggregationResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentStreamVersion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; aggregationResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentStreamVersion&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 2. Run business logic&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newEvents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 3. Check if any changes have been made&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newEvents&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    newEvents&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    newState&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    nextExpectedStreamVersion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; currentStreamVersion&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    createdNewStream&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 4. Append result to the stream&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appendResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; eventStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;appendToStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  newEvents&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     expectedStreamVersion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; currentStreamVersion&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 5. Return result with updated state&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;appendResult&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  newEvents&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  newState&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; newEvents&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you’re not doing Event Sourcing then it’s the same stuff; it could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// 1. Aggregate the stream&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; findResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; repository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findById&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;recordId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Use the aggregate state&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; findResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;initialState&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentStreamVersion &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; findResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentVersion&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 2. Run business logic&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 3. Check if any changes have been made&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isIgnoredResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    newState&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    nextExpectedVersion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; currentVersion&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    createdNewRecord&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 4. Append result to the stream&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; storeResult &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; repository&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  recordId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     expectedVersion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; currentVersion&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 5. Return result with updated state&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;storeResult&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  newState&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Essentially, we’re loading the current state. If it doesn’t exist, we’re setting the default initial state. For Event Stream, not existing will mean an empty events array; for the classical approach, it can be null. Thanks to assigning explicit initial state. We’re making things explicit. Thanks to that, we can check whether the record exists in our business logic.&lt;/p&gt;
&lt;p&gt;We also get the current version of the record and use it as the expected version when appending event(s)/performing the state update. We’re using &lt;a href=&quot;/en/optimistic_concurrency_for_pessimistic_times/&quot;&gt;Optimistic Concurrency&lt;/a&gt; here. We’re telling our storage engine to only accept the update if the stream/record version in the database is the same as the expected one. Otherwise, it should reject the update (usually by throwing an exception). Thanks to that, we’re ensuring that if another change happens in parallel, our update won’t be accepted. All mature storage engines support &lt;a href=&quot;/en/optimistic_concurrency_for_pessimistic_times/&quot;&gt;Optimistic Concurrency&lt;/a&gt;. If they don’t, then run away; they’re not mature!&lt;/p&gt;
&lt;h2 id=&quot;idempotency-and-optimistic-concurrency&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#idempotency-and-optimistic-concurrency&quot; aria-label=&quot;idempotency and optimistic concurrency permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Idempotency and Optimistic Concurrency&lt;/h2&gt;
&lt;p&gt;That’s cool stuff, but we already said that throwing exceptions is not a perfect solution. What’s more, the change happening in parallel doesn’t have to be conflicting. In our current business rules, the only conflicting change is when a guest is checked out. If we’re recording charges or payments, then the guest stay is still running, and we should safely succeed. How to handle that?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The solution for that is the retry policy.&lt;/strong&gt; We could wrap our command handling in the retry that would check if concurrency error was thrown.&lt;/p&gt;
&lt;p&gt;Let’s say that we have the following wrapper for the command handling:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; CommandHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; EventType &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;evolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;initialState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    eventStore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; StreamEvent&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;CommandHandlerResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// (...) Command handling code you saw above&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We can define it for our guest stay as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;CommandHandler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; initialState &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And use it in our endpoint for checking in guest as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; guestStayAccountsApi &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    eventStore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WebApiSetup &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;/guests/:guestId/stays/:roomId&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckInRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 1. Prepare command &lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; guestStayAccountId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;toGuestStayAccountId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;guestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
        request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roomId
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckIn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          guestId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;guestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           roomId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roomId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; now&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// 2. Run command handling logic&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;checkIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        
      &lt;span class=&quot;token comment&quot;&gt;// 3. If no error was thrown, return success&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Created&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        url&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/guests/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;guestId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/stays/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;roomId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/periods/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;formatDateToUtcYYYYMMDD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;now&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With this approach, we have a handle method, which is an abstraction for command handling. Thanks to the repeatable pattern of handling business logic, we can make it generic, leaving business logic focused and explicit. As long it returns events, then we’re good.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Getting back to the retries.&lt;/strong&gt; As our command handler is just a function, we could decorate it with retry code.&lt;/p&gt;
&lt;p&gt;Let’s start by defining the general retry policy. I’m using the &lt;a href=&quot;https://www.npmjs.com/package/async-retry&quot;&gt;async-retry&lt;/a&gt; npm package. If you’re in C#, check &lt;a href=&quot;https://www.pollydocs.org&quot;&gt;Polly&lt;/a&gt;, and in Java, check &lt;a href=&quot;https://github.com/spring-projects/spring-retry&quot;&gt;Spring Retry&lt;/a&gt;. Each environment has a recommended package to handle that.&lt;/p&gt;
&lt;p&gt;The only thing I added is a bit easier way to pass function to check if we should retry on a specfic error.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; retry &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;async-retry&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AsyncRetryOptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; retry&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Options &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  shouldRetryError&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; NoRetries&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AsyncRetryOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; retries&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; asyncRetry &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  opts&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AsyncRetryOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;opts &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; opts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;retries &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;retry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bail&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;opts&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;shouldRetryError &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;opts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shouldRetryError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;bail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    opts &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; NoRetries&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s also define a check for concurrency error. It could look as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isExpectedVersionConflictError &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  error &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ExpectedVersionConflictError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Depending on your storage engine this function will differ, for instance for PostgreSQL change it could be:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; isExpectedVersionConflictError &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  error&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  error &lt;span class=&quot;token keyword&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;code&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; error &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;code &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;23505&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Having that, we can adjust our command handler to:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultRetryOptions&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AsyncRetryOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    retries&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    minTimeout&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    factor&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    shouldRetryError&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; isExpectedVersionConflictError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; CommandHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; EventType &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;evolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;initialState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    eventStore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; StreamEvent&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; retry&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AsyncRetryOptions &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;CommandHandlerResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; 
    &lt;span class=&quot;token function&quot;&gt;asyncRetry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// (...) Command handling code you saw before&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;retry &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; defaultRetryOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;No other change is needed; our logic can remain the same. Of course, we can make it more sophisticated or default to no retries and only make retries in specific cases, but I think you get the idea.&lt;/p&gt;
&lt;p&gt;It’s essential to the business logic and the whole command handling flow. Without reading again, we won’t get the new state and the current stream version, and the update will fail.&lt;/p&gt;
&lt;h2 id=&quot;handling-duplicates&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#handling-duplicates&quot; aria-label=&quot;handling duplicates permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Handling duplicates&lt;/h2&gt;
&lt;p&gt;Retries are nice, but how do we handle duplicates? Won’t retries generate new records? It may happen if we were always generating new random IDs for our guest stay. For instance:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; randomUUID &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;node:crypto&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; guestStayAccountId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CheckIn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    guestId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;guestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    roomId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roomId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; now&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;checkIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But that’s not what we did, we did:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; guestStayAccountId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;token function&quot;&gt;toGuestStayAccountId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;guestId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
  request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;roomId
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;where &lt;em&gt;toGuestStayAccountId&lt;/em&gt; creates a predictable id based on provided data&lt;/strong&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;toGuestStayAccountId&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  guestId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  roomId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;guest_stay_account-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;guestId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;roomId&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The stay can be represented by guest and room (it could also be reservation ID or whatever is predictable).&lt;/p&gt;
&lt;p&gt;Thanks to that, no matter how many times we retry, we’ll always be accessing the same record; the combination of business logic, transactions, and optimistic concurrency will ensure that we won’t end up in the wrong state.&lt;/p&gt;
&lt;h2 id=&quot;idempotency-handling-in-updates&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#idempotency-handling-in-updates&quot; aria-label=&quot;idempotency handling in updates permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Idempotency handling in updates&lt;/h2&gt;
&lt;p&gt;We can also apply the same pattern to the update operation, let’s say we have the following code for recording charge:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recordCharge&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chargeId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; amount &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metadata &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RecordCharge&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ChargeRecorded &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;assertIsCheckedIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ChargeRecorded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      chargeId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      recordedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; metadata&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; assertIsCheckedIn &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; CheckedIn &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotExisting&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Guest account doesn&apos;t exist!&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;`Guest account &lt;span class=&quot;token keyword&quot;&gt;is&lt;/span&gt; already checked out&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This function is not idempotent. Of course, we will ensure that we’re not recording charges for not existing or checked-out accounts, but this code won’t help us for not recording double charges. Doubled charges are not something we’d like to have in the financial module. Or anywhere.&lt;/p&gt;
&lt;p&gt;To detect it, we’d need information about the recorded transaction ids. Then, we could check if the following transaction hasn’t been handled already.&lt;/p&gt;
&lt;p&gt;Let’s change our state definition to include transaction ids:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NotExisting&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotExisting&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CheckedIn&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 
  balance&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// new property&lt;/span&gt;
  transactionIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CheckedOut&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedOut&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GuestStayAccount&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; NotExisting &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; CheckedIn &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; CheckedOut&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; initialState &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccount &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotExisting&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We also need to change our &lt;em&gt;evolve&lt;/em&gt; function, which we’re using to define &lt;a href=&quot;/en/how_to_get_the_current_entity_state_in_event_sourcing/&quot;&gt;how we build our state from events&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;evolve&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccountEvent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccount &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckedIn&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotExisting&apos;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; balance&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ChargeRecorded&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedIn&apos;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            balance&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;balance &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
            &lt;span class=&quot;token comment&quot;&gt;// new line&lt;/span&gt;
            transactionIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transactionIds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chargeId &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;PaymentRecorded&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedIn&apos;&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            balance&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;balance &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;// new line&lt;/span&gt;
            transactionIds&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transactionIds&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paymentId &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedIn&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedOut&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;GuestCheckoutFailed&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; _notExistingEventType&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;never&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For the classical approach, you’d need to extend your table schema.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And now, the question? How can we get the charge id?&lt;/strong&gt; The sender should generate it. Who’s &lt;em&gt;“the sender”&lt;/em&gt;? For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;web page,&lt;/li&gt;
&lt;li&gt;mobile application,&lt;/li&gt;
&lt;li&gt;other module,&lt;/li&gt;
&lt;li&gt;external payment gateway.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’re good as long as the same charge has the same charge id in the command data. If we’re retrying operations from our web page, we should ensure that we’re not recording the new charge id on each retry.&lt;/p&gt;
&lt;p&gt;The updated logic handling idempotency correctly can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recordCharge&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chargeId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; amount &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metadata &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RecordCharge&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ChargeRecorded &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;assertIsCheckedIn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// idempotence check&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transactionIds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;chargeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ChargeRecorded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      chargeId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      recordedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; metadata&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;look-ma-no-exceptions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#look-ma-no-exceptions&quot; aria-label=&quot;look ma no exceptions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Look Ma’ No Exceptions!&lt;/h2&gt;
&lt;p&gt;Ok, but what about those errors? Many people don’t like exceptions, and that’s for good reasons. Some languages even don’t allow us to return them. How would I model errors in general?&lt;/p&gt;
&lt;p&gt;It depends, I see a few ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if they’re “just” errors and I’m in an environment like C# or Java, then I’d throw an exception and map it to the status. For functional environment or Go, Rust, I’d return the result. I’m not a big fan of Railway Oriented Programming in languages that don’t have the pipeline operator. Without it, it’s not ergonomic.&lt;/li&gt;
&lt;li&gt;The same for events that are part of the &lt;a href=&quot;/en/saga_process_manager_distributed_transactions/&quot;&gt;asynchronous workflow&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;if they represent idempotent handling, then return an empty array or explicitly ignore the object.&lt;/li&gt;
&lt;li&gt;if they’re important to business, I’d model them as events and store them in the stream.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s discuss the last option, and let’s say that knowing that the charge was rejected is important information for businesses, as they’d like to track those failure scenarios. They can be part of antifraud detection or similar processes.&lt;/p&gt;
&lt;p&gt;Having that we could define additional event type:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ChargeFailed&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ChargeFailed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    chargeId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    guestStayAccountId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotCheckedIn&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AlreadyCheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    failedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PaymentFailed&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;PaymentFailed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    paymentId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    guestStayAccountId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotCheckedIn&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AlreadyCheckedOut&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    failedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// (...) other event types&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GuestStayAccountEvent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; GuestCheckedIn
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ChargeRecorded
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ChargeFailed &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;= new&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PaymentRecorded
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; PaymentFailed &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;= new&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; GuestCheckedOut
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; GuestCheckoutFailed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Instead of throwing exception, we could return now an event:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recordCharge&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; chargeId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; amount &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metadata &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RecordCharge&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; GuestStayAccount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ChargeRecorded &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ChargeFailed &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// failure&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;CheckedIn&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ChargeFailed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        chargeId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        reason&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;NotExisting&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
          &lt;span class=&quot;token string-property property&quot;&gt;&apos;NotCheckedIn&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;AlreadyCheckedOut&apos;&lt;/span&gt;
        failedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; metadata&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;transactionIds&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;includes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;chargeId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ChargeRecorded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      chargeId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; amount&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      recordedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; metadata&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By default, this event will be stored in the stream. Still, even if we don’t want to store it in the stream, we can decide not to throw exceptions but model all edge cases as events. Then, we can decide on the application layer whether to store it or not. Maybe we would like to just log it.&lt;/p&gt;
&lt;p&gt;We can orchestrate it like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    eventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
    guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recordCharge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token comment&quot;&gt;// check if it&apos;s failed event&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ChargeFailed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// let&apos;s ignore it, we could also throw error here&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;generic-idempotency-handling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#generic-idempotency-handling&quot; aria-label=&quot;generic idempotency handling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Generic idempotency handling&lt;/h2&gt;
&lt;p&gt;I think business logic is the proper place to check the business rules, and not handling doubled charges is actually a business rule. Still, many people prefer to handle it generically. Let’s discuss how to do it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We could store the handled command ids in the external store and run only business logic if it wasn’t already handled.&lt;/strong&gt; The store could be defined as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IdempotencyKeyStore&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; timeoutInMs&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;boolean&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The store has the following methods:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;tryLock&lt;/em&gt; - that tries to lock the specified key for a certain period of time. Returns true if the lock was acquired and false otherwise.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;accept&lt;/em&gt; -  accepts the key lock after successful handling.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;release&lt;/em&gt; - releases the lock in case of handling failure.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What’s most important is that those operations should be thread-safe and atomic. Without that, we may face race conditions. The safest safest option is to use a distributed lock, e.g., Redis or a relational database.&lt;/p&gt;
&lt;p&gt;Having that, we can define our wrapper as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IdempotentOptions&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  store&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IdempotencyKeyStore&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;defaultResult&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  timeoutInMs&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;idempotent&lt;/span&gt; &lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IdempotentOptions&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timeoutInMs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; defaultResult&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; store &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;//Attempt to acquire the lock for the specified key&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; appended &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;tryLock&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; timeoutInMs &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// If it was used, return the default result&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;appended&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defaultResult&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// run handler&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Mark operation as successful&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Release key if it was used already&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; store&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re passing the function to wrap together with options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;idempotency store,&lt;/li&gt;
&lt;li&gt;idempotency key,&lt;/li&gt;
&lt;li&gt;default result if the command was already handled,&lt;/li&gt;
&lt;li&gt;optional lock timeout.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having that, we:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Attempt to acquire the lock for the specified key.&lt;/li&gt;
&lt;li&gt;If the attempt wasn’t successful return the default result.&lt;/li&gt;
&lt;li&gt;If we acquired lock, then run the business logic.&lt;/li&gt;
&lt;li&gt;If it fails, release the lock. We don’t want to lock the key, let someone fix the state condition, and retry later.&lt;/li&gt;
&lt;li&gt;If it was successful, accept the lock and return the result.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The example lock store based on Redis could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultTryLockPeriod &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1 second&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultLockPeriod &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;86400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1 day&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; redisStore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IdempotencyKeyStore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  tryLock&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; timeoutInMs&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; redis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;in-progress&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;NX&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;EX&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;timeoutInMs &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; defaultTryLockPeriod&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;OK&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// Update the key to &quot;completed&quot; status with a longer TTL&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; redis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;completed&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;XX&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;EX&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; defaultLockPeriod&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// 1 day TTL&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; 
    redis&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;del&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;key&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Having that, we could decorate our command handler with idempotent handling:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; CommandHandler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; EventType &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;evolve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StreamEvent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;initialState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    idempotencyKeyStore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IdempotencyKeyStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    eventStore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function-variable function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; State&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; StreamEvent&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; retry&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AsyncRetryOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; idempotencyKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;CommandHandlerResult&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; 
    &lt;span class=&quot;token function&quot;&gt;idempotent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;asyncRetry&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// (...) Command handling code you saw before&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        options&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;retry &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; defaultRetryOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  
        idempotencyKeyStore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IdempotencyKeyStore&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        key&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;IdempotencyKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token function-variable function&quot;&gt;defaultResult&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currentStreamVersion &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; eventStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;aggregateStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            initialState&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

          &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            newEvents&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            newState&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            nextExpectedStreamVersion&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; currentStreamVersion&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            createdNewStream&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nothing fancy besides getting the current state in case we already handled this command.&lt;/p&gt;
&lt;p&gt;The endpoint will look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; guestStayAccountsApi &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    eventStore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; EventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    idempotencyKeyStore&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; IdempotencyKeyStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; WebApiSetup &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;/guests/:guestId/stays/:roomId/periods/:checkInDate/charges&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RecordChargeRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; guestStayAccountId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseGuestStayAccountId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; RecordCharge &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;RecordCharge&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          chargeId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chargeId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          amount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amount&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; now&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        eventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
        idempotencyKeyStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
        guestStayAccountId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recordCharge&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; idempotencyKey&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;chargeId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NoContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And guess what? That’s also a way how external API providers are handling it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stripe.com/docs/api/idempotent_requests&quot;&gt;Stripe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.visa.com/capabilities/vpp/docs-how-to&quot;&gt;Visa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.checkout.com/docs/payments/manage-payments/retry-a-payment&quot;&gt;Checkout&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, it’s up to you how you compose; you could even make it more generic and part of your HTTP middleware based on the &lt;a href=&quot;https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/&quot;&gt;Idempotency-Key header&lt;/a&gt;. The choice is yours, but the logic will be the same.&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h2&gt;
&lt;p&gt;Idempotency isn’t just a buzzword—it’s a practical way to handle operations that should only happen once, no matter how many times they’re retried. Whether it’s checking in a guest, recording charges, or processing API calls, the key is ensuring consistency without breaking the flow. By designing systems that handle duplicates gracefully, use optimistic concurrency, or apply idempotency keys, we can avoid annoying edge cases and give users a better experience. It’s not about exceptions versus results—it’s about designing systems that actually work in the real world.&lt;/p&gt;
&lt;p&gt;We went through the various ways to handle it. Both explicitly and generically. From building predictable state machines, handling it in business logic,  to leveraging Optimistic Concurrency, retries and distributed locks.&lt;/p&gt;
&lt;p&gt;As tempting as it is to use generic handling, as you saw above, idempotency handling is highly dependent on the business rules; those rules tend to change. If we use generic handling, we’re always adding additional overhead, even if most of our applications rarely have idempotency issues. That’s something to consider, as it can also increase costs in cloud environments.&lt;/p&gt;
&lt;p&gt;Whether you’re processing financial transactions, integrating with external APIs, or handling user commands, the principles remain the same: make the system resilient, maintain consistency, and prioritize user experience.&lt;/p&gt;
&lt;p&gt;Because, in the end, it’s not about whether you throw exceptions or return results—it’s about understanding the nuances of the business domain and designing solutions that respect those rules.&lt;/p&gt;
&lt;p&gt;So, how are you making your command handling idempotent? If you haven’t thought about it yet, maybe now’s the time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you need to help in applying those practices, I’m here to help.&lt;/strong&gt; Check my &lt;a href=&quot;/en/training/&quot;&gt;training&lt;/a&gt; page. A workshop is the most effective way to jump-start, I’m also doing consulting and mentoring, &lt;a href=&quot;mailto:oskar@event-driven.io&quot;&gt;drop me an e-mail!&lt;/a&gt; and we’ll find the best way to help you and your team.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And guess what, all of those you saw in the article, is already available in &lt;a href=&quot;https://github.com/event-driven-io/emmett/&quot;&gt;Emmett&lt;/a&gt;!&lt;/strong&gt; Come join us, we have cookies! &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Join our Discord channel&lt;/a&gt; and discuss further questions!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Building your own Ledger Database]]></title><description><![CDATA[Building your own database sounds like moronic move. Yet, that’s the advice I gave recently on the Architecture Weekly Discord channel. Why…]]></description><link>https://event-driven.io/en/building-your-own-ledger-database/</link><guid isPermaLink="false">https://event-driven.io/en/building-your-own-ledger-database/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Building your own database sounds like moronic move.&lt;/strong&gt; Yet, that’s the advice I gave recently on the Architecture Weekly Discord channel. Why did I do it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One member asked about the recommendation for replacing Amazon QLDB. What’s Amazon QLDB?&lt;/strong&gt; Apparently, it’s the database type that Amazon is sunsetting next year. It was also the database that the question asker was using in most of his project services.&lt;/p&gt;
&lt;p&gt;Jokes aside, Amazon states that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Amazon Quantum Ledger Database (Amazon QLDB) is a fully managed ledger database that provides a transparent, immutable, and cryptographically verifiable transaction log owned by a central trusted authority. You can use Amazon QLDB to track all application data changes, and maintain a complete and verifiable history of changes over time.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And suggests &lt;a href=&quot;https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/&quot;&gt;migrating it to Amazon Aurora PostgreSQL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Which is an option, but I suggested something else. Before we go to my suggestion, let’s discuss what are actually Ledger Databases. We talk about them in the previous editions, so &lt;a href=&quot;https://www.architecture-weekly.com/p/architecture-weekly-169-4th-march&quot;&gt;#169 - 4th March 2024&lt;/a&gt; and &lt;a href=&quot;https://www.architecture-weekly.com/p/architecture-weekly-181-27th-may&quot;&gt;#181 - 27th May 2024&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-are-ledger-databases&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-are-ledger-databases&quot; aria-label=&quot;what are ledger databases permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What Are Ledger Databases?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ledger databases are specialized data stores designed to provide an immutable and verifiable record of transactions over time.&lt;/strong&gt; They ensure that every change to the data is recorded and cannot be tampered with, making them ideal for applications where data integrity and audibility are critical. Many industries have such legal requirements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unlike traditional databases that allow data to be overwritten or deleted, ledger databases use an append-only model.&lt;/strong&gt; Every transaction is added as a new entry, and previous entries remain untouched. This creates a complete history of all data changes, which is crucial for compliance, auditing, and tracing.&lt;/p&gt;
&lt;p&gt;Under the hood, ledger databases combine familiar database technologies with cryptographic techniques to guarantee data integrity. Each transaction is often cryptographically hashed and linked to the previous one, forming a chain that makes tampering evident.&lt;/p&gt;
&lt;h2 id=&quot;use-cases-for-ledger-databases&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#use-cases-for-ledger-databases&quot; aria-label=&quot;use cases for ledger databases permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Use cases for Ledger Databases&lt;/h2&gt;
&lt;p&gt;So, where do ledger databases fit into our architectural toolbox?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In business domains where you have to prove that no one even has a slight option to mutate the operations log.&lt;/strong&gt; In some of them, user access management is not enough. You’d better use databases natively made write-ahead, not allowing you to mutate or delete anything. For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Financial Services:&lt;/strong&gt; Banks, payment processors, and financial institutions need to maintain accurate and immutable records of transactions for compliance and auditing purposes. A ledger database ensures that every deposit, withdrawal, or transfer is permanently recorded.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Healthcare Records:&lt;/strong&gt; Patient data must be accurate, secure, and compliant with regulations like &lt;a href=&quot;https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act&quot;&gt;HIPAA&lt;/a&gt;. Ledger databases help maintain a record of patient information and medical history, ensuring that any access or modification is immutably logged.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Government and Legal Records:&lt;/strong&gt; Official documents like property titles, court records, and legislative changes require integrity and traceability. An immutable ledger provides a trustworthy record that can withstand legal scrutiny.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Supply Chain Management:&lt;/strong&gt; Tracking the movement of goods from origin to destination requires transparency and trust among multiple parties. Ledger databases can record each step in the supply chain, providing a verifiable history that stakeholders can rely on.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Immutable log of data, ummmm. Do I smell Event Sourcing?&lt;/p&gt;
&lt;h2 id=&quot;ledger-databases-vs-event-stores-and-event-sourcing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#ledger-databases-vs-event-stores-and-event-sourcing&quot; aria-label=&quot;ledger databases vs event stores and event sourcing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Ledger Databases vs. Event Stores and Event Sourcing&lt;/h2&gt;
&lt;p&gt;There’s often confusion between ledger databases and event stores used in event sourcing. While both deal with immutable data and event histories, they serve different purposes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Event Sourcing is a design pattern in which outcomes of the business logic are stored as events.&lt;/strong&gt; Then, they’re used for making the next decisions. They’re stored in sequences called streams. That can sound a bit scary, but in other words, streams represent records in classical databases. Events are the state; there’s no other state. Thus, the name Event Sourcing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In Event Sourcing flow looks as follows:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;read all events from the stream,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;interpret it getting the current state,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;run business logic,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;store the new event(s) in the same stream you’ve read from.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;rinse/repeat.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;So, Event Stores are essentially Key-Value databases: The key is the record id, and the value is the sequence of events.&lt;/strong&gt; And they’re used for transactional operations focused on business logic. Read more in my articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/event_stores_are_key_value_stores/&quot;&gt;Event stores are key-value databases, and why that matters&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/audit_log_event_sourcing/&quot;&gt;Is the audit log a proper architecture driver for Event Sourcing?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how_to_get_the_current_entity_state_in_event_sourcing/&quot;&gt;How to get the current entity state from events?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ledger databases, on the other hand, are specialized databases optimized for storing transactions, often with financial data semantics. They focus on accurately recording transactions, with an emphasis on data integrity and compliance, and often include cryptographic verification. So, they’re more like audit logs with much different data granularity than event stores.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your main driver is to record and keep immutable data as side effects of your business logic, not your decision model.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Event stores are also general-purpose databases, while ledger databases are tailored for specific domains like finance. For example, financial ledger databases accurately record monetary transactions and balances and ensure consistency in financial operations. Going down the niche allows for domain-specific optimizations and limits the scope of usage.&lt;/p&gt;
&lt;h2 id=&quot;custom-ledger-databases&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#custom-ledger-databases&quot; aria-label=&quot;custom ledger databases permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Custom Ledger Databases&lt;/h2&gt;
&lt;p&gt;Those optimisation needs drove Uber and Stripe to build their own Ledger Databases; I’ve gone through them in linked editions.&lt;/p&gt;
&lt;h3 id=&quot;stripe-ledger&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#stripe-ledger&quot; aria-label=&quot;stripe ledger permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Stripe Ledger&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Stripe built its own ledger system focused on storing and tracking financial data.&lt;/strong&gt; Stripe operates at a massive scale, processing billions of events daily. They had to standardize the representation of money movement. With various systems and partners involved, they required a unified way to represent transactions despite the complexity and imperfections of real-world financial data.&lt;/p&gt;
&lt;p&gt;The ledger had to be an immutable source of truth that could be relied upon for internal operations and external audits. So, they also had to ensure data integrity and trustworthiness.&lt;/p&gt;
&lt;p&gt;The real world is imperfect, and they had to embrace it. Banking and network partners can provide malformed reports or errors, and they needed custom system to keep these imperfections manageable.&lt;/p&gt;
&lt;p&gt;Stripe’s Ledger is an immutable log of events that models internal data-producing systems with common patterns like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;State Machine Representation:&lt;/strong&gt; Ledger encodes producer systems as state machines, modeling behavior as logical fund flows—the movement of balances between accounts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Double-Entry Bookkeeping:&lt;/strong&gt; They applied traditional accounting principles to validate money movement, ensuring that credits and debits balance out.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Quality Platform:&lt;/strong&gt; On top of Ledger, they built a platform to unify detection of money movement issues and provide response tooling. This ensures proactive alerting and surfaces issues promptly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As mentioned, they operate on an extreme scale. Ledger processes five billion events per day. To handle this scale, they optimized their systems for high throughput and low latency. By using double-entry bookkeeping and immutable logs, they ensured that every transaction is accounted for and that the system can detect discrepancies.&lt;/p&gt;
&lt;p&gt;Read more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://stripe.com/blog/ledger-stripe-system-for-tracking-and-validating-money-movement&quot;&gt;Ledger: Stripe’s system for tracking and validating money movement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ubers-ledgerstore&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#ubers-ledgerstore&quot; aria-label=&quot;ubers ledgerstore permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Uber’s LedgerStore&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Similarly to Stripe, Uber developed its own ledger system, LedgerStore. To better meet its needs, Uber moved from using DynamoDB to its custom solution&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;At Uber’s scale, DynamoDB became expensive. Managing petabytes of data and trillions of indexes was not cost-effective. DynamoDB’s limitations led to issues like hot partitions and write throttling, requiring complex workarounds that increased system complexity.&lt;/p&gt;
&lt;p&gt;Uber needed a system that ensured data integrity, handled massive data volumes efficiently, and provided better performance.&lt;/p&gt;
&lt;p&gt;Similarly, they needed to ensure the data integrity and correctness guarantees, ensuring that individual records are immutable and corrections are trackable. To do that, they came up with their own ledger database. They designed an indexing system capable of handling trillions of indexes, supporting various access patterns via different types of indexes (strongly consistent, eventually consistent, time-range indexes).&lt;/p&gt;
&lt;p&gt;Uber migrated over a trillion ledger entries from DynamoDB to LedgerStore. They used &lt;a href=&quot;https://spark.apache.org/&quot;&gt;Apache Spark&lt;/a&gt; for incremental backfill, processing data in chunks to avoid system overloads.&lt;/p&gt;
&lt;p&gt;To ensure accuracy, Uber used shadow validation, comparing outputs from the old and new systems to detect discrepancies, achieving a 99.99% accuracy rate.&lt;/p&gt;
&lt;p&gt;They encountered performance issues due to poorly distributed index data and inefficient indexing methods. To address this, they optimized partition keys and revised their indexing approach to reduce unnecessary scans.&lt;/p&gt;
&lt;p&gt;Read more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.uber.com/en-AU/blog/migrating-from-dynamodb-to-ledgerstore/&quot;&gt;Uber - How LedgerStore Supports Trillions of Indexes at Uber&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.uber.com/en-AU/blog/migrating-from-dynamodb-to-ledgerstore/&quot;&gt;Uber - Migrating a Trillion Entries of Uber’s Ledger Data from DynamoDB to LedgerStore&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-was-the-discord-member-use-case&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-was-the-discord-member-use-case&quot; aria-label=&quot;what was the discord member use case permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What was the Discord member use case?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The person who asked the question faced the challenge of migrating over 100 ledgers before Amazon QLDB was deprecated.&lt;/strong&gt; The company operates in a regulated environment, providing imaging solutions for healthcare and pharmaceutical clients.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;They need to:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Log critical information about actions taken on data, including action type, timestamp, source, outcome, and the identity of the individual or entity who carried out the action.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Implement measures to protect the accuracy and completeness of audit logs and verify the integrity of the data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Restrict data access to authorized individuals using appropriate access controls.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provide an audit trail that cannot be altered or deleted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ability to deploy within specific legal jurisdictions to comply with data residency requirements.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Currently, they use a combination of AWS services:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DynamoDB for storing data.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Kinesis for streaming changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lambda functions to write to the ledger (either in the same account or cross-account).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Given the scale and regulatory requirements, moving to a centralized solution like Amazon Aurora PostgreSQL might be expensive and may not meet all needs. For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;you know that I love PostgreSQL, but you cannot easily prove that modifying data is not possible,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;they are built for transactional operations, not great for massive batch operations,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;they have maximum limits of size (PG - 32 TB of data),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;s3-and-duckdb-as-ledger-database&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#s3-and-duckdb-as-ledger-database&quot; aria-label=&quot;s3 and duckdb as ledger database permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;S3 and DuckDB as Ledger Database&lt;/h2&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6b8ba4a661b54b5ea52789f5e52b5b53/e0774/2024-11-11-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEIUlEQVR42i2SXUyTBxSGe7XsYjfEbNnlsguXJbol21ymcyywqfEPdUoQhjBgBSlQlLYUPiitUlrKb6G00lKkwEehpZSClEKJMqiUjDEdmvgHJOJfcKBebPfPvmW7OzfnyfOe88o6NFl01Ffjtl7CWqWgVpGGqaKEAY8TV7MOt6kMd70Gh1mFo64ch/Azsdg862trrK6ts/F8k4lffmd4Ko5vcgFZtU2Juq0AtT2HtMpDJJ7bxeGizziQ8SX5tamUe+RcsGdhcWjQOYrQdaq4HrvB/XsPWFpaYuPJMyKxFXyROP5/gV1eL91BP/7rUU5lnyX5SBLlFhUFigzyc/IZuniFQZcXV8RKY1iPddLCfHyBV1vbrK0+YuvVa8nwFv7Iwn/AAX8A/+go09fn6GhsxCxocbc0Y1CXYDM3ENX1Md87Tc1YPurQCQxj2WTmnMNYW4e13cbi8l3Cc7fxT0nAyE1kgyNjiH4fEzOzdHe7sZoN9NvasGhK8fR2MTU3yWJ8EcEnJ0/8Du1wOnJ5NoVZmezdux+nGGZy/g6B6UXJMI5M9A8z4B9iMDiGyVzH5YqL6MuKaZVmUexjZjrC4sICE7MhPOOdjEQHCPRfxWNrRVdWiuiblCKvSMD/n+Jw2GhobUIwCFTqKnG11FOhyKKlw0r/oIjX62Ik2Mfqw1Vicze5c/sOnvZWeqyt2Iw1+Eck4OwfEvAm/vA8MqHWSpFg4qJGQCPoUakEVFVGTIoCeiW4Z+gKHtFKyDfATHSG8YkwzYZqmmsqsEu3Hh6NMBi6IdlJhuFFZE3J9RxLXyIp5yEH89c5UriOPn0c58kaTEYtXRMOusc7aOqrkiI7GL8WwdFuxa46z2ygn+nhHoI9dkaHuhgL+JDlH52SlrsoOb9CieIuzd870ZyaIVW5TPnlIvTBElpDRrQ956gf0RIIjHEtFGRduYsX6QlsNn7AlmUH27oE/sx5H9lXGY9xXwgT25dHKMVIat4jEgv+4kzhHOXlCjydnYhuNz2OdgL+IINDQcIjQdZqPuKl8gs21bvYKt/J5um3eSO8hazu61bc2X0MeZ8SrR1n0hRG0/I3J3JnycxMwVJZhsukx6JX09Pbh9jvJyAOcF+3m82mZJ4d2s3L4oO86T/M6wbJsPhQgILqDbSmJwht23SccaP/MUpybpyS3NM0V6tplKpkFNTYbXa6XR56RZF75z/hRe5etno1bDcoeHO1im3bp8g+T/6V1Mxljqfd4ljGXfYce4B5nxNlmhdNcRaleekYtCqcjk6cdhdtV9xM6Qw8SvmQjUuJPLHs47l5D09TEnihfAfZ/sQbHDga5+TZZdJ+WiH5hxjHD09SmFTJN9/uoVQpx1JvRK8TEHRV9MrziWor+K34Yx7Kd7Cm2sn6hXd5XJTA09Pv8Q/ZIyjRUeSoLwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/6b8ba4a661b54b5ea52789f5e52b5b53/507e6/2024-11-11-cover.webp 200w,
/static/6b8ba4a661b54b5ea52789f5e52b5b53/28a80/2024-11-11-cover.webp 400w,
/static/6b8ba4a661b54b5ea52789f5e52b5b53/8d2ea/2024-11-11-cover.webp 800w,
/static/6b8ba4a661b54b5ea52789f5e52b5b53/7464f/2024-11-11-cover.webp 860w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/6b8ba4a661b54b5ea52789f5e52b5b53/36ca5/2024-11-11-cover.png 200w,
/static/6b8ba4a661b54b5ea52789f5e52b5b53/a3397/2024-11-11-cover.png 400w,
/static/6b8ba4a661b54b5ea52789f5e52b5b53/a331c/2024-11-11-cover.png 800w,
/static/6b8ba4a661b54b5ea52789f5e52b5b53/e0774/2024-11-11-cover.png 860w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/6b8ba4a661b54b5ea52789f5e52b5b53/a331c/2024-11-11-cover.png&quot;
            alt=&quot;2024 11 11 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I suggested considering Amazon S3 as the storage for audit logs. Here’s why:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Immutability:&lt;/strong&gt; S3 can be configured immutable using &lt;a href=&quot;https://aws.amazon.com/blogs/storage/protecting-data-with-amazon-s3-object-lock/&quot;&gt;Object Lock with WORM (Write Once Read Many)&lt;/a&gt; policies, making it suitable for storing audit trails that cannot be altered or deleted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cost-Effective:&lt;/strong&gt; S3 storage is relatively cheap, especially compared to relational databases. You pay for what you use, and there are different storage classes to optimize costs based on access patterns. I went extensively with cost analysis in: &lt;a href=&quot;/en/show-me-the-money-practically-navigating/&quot;&gt;Show me the money! Practically navigating the Cloud Costs Complexity&lt;/a&gt;. Here, the pricing model works in favour of the audit log, as you can batch data. Also, you can use cheaper tiers to store audit logs, as you don’t need to access them on a daily basis.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scalability:&lt;/strong&gt; S3 scales virtually infinitely, handling vast amounts of data without the need to provision capacity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Residency and Compliance:&lt;/strong&gt; S3 supports data residency requirements by allowing you to choose the regions where data is stored and applying specific compliance configurations. Read more in &lt;a href=&quot;https://docs.aws.amazon.com/prescriptive-guidance/latest/strategy-aws-semicon-workloads/meeting-data-residency-requirements.html&quot;&gt;AWS documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Batch Operations:&lt;/strong&gt; Since they already have an intermediate step storing data in DynamoDB, they could batch operations and store them in S3, reducing costs and simplifying the architecture.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Querying Data with DuckDB:&lt;/strong&gt; For querying data in S3, tools like DuckDB can be used. DuckDB is a fast, embeddable database optimized for analytical queries on large datasets stored in files like &lt;a href=&quot;https://parquet.apache.org/&quot;&gt;Parquet&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;challenges-and-solutions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#challenges-and-solutions&quot; aria-label=&quot;challenges and solutions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Challenges and solutions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Of course, this solution is not perfect. It’s just fine-tuned for this specific scenario.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In a nutshell, S3 is immutable, so that should be easier to prove than a relational database. It’s really cheap. Especially if you have an intermediate step in storing data inside the database already (the Discord member already used DynamoDB for transactional data), then you could batch operations.&lt;/p&gt;
&lt;p&gt;Querying large datasets in S3 can have latency. To optimize query performance, using file format like &lt;a href=&quot;https://parquet.apache.org/&quot;&gt;Parquet&lt;/a&gt; and partitioning data is a must. Why? I wrote longer on that in &lt;a href=&quot;/en/using-s3-but-not-the-way-you-expected/&quot;&gt;Using S3 but not the way you expected. S3 as strongly consistent event store&lt;/a&gt;. Partitioning should also help to set up the granular authorisation rules for sensitive audit logs. Se allow that through IAM policies, using encryption, and monitoring access logs.&lt;/p&gt;
&lt;p&gt;S3 provides eventual consistency for overwriting PUTS and DELETES, but since we’re using an append-only model with new objects, consistency should not be an issue.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DuckDB is a relatively new player in the game.&lt;/strong&gt; I haven’t used it yet in production. Still, all the people I know who have used it say only good things. It’s already used in production by many and &lt;a href=&quot;https://duckdb.org/docs/extensions/httpfs/s3api.html&quot;&gt;has good support for cloud-native BLOB storage like the S3&lt;/a&gt;. It also supports the &lt;a href=&quot;https://parquet.apache.org/&quot;&gt;Parquet file format&lt;/a&gt; that enhances the querying capabilities and decreases the file size.&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://towardsdatascience.com/duckdb-and-aws-how-to-aggregate-100-million-rows-in-1-minute-3634eef06b79&quot;&gt;DuckDB and AWS — How to Aggregate 100 Million Rows in 1 Minute&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://duckdb.org/docs/data/parquet/overview.html&quot;&gt;Reading and Writing Parquet Files&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Of course, it’d be good to do a Proof of Concept before making a final decision and compare with AWS-based solutions to move from dynamo to S3, e.g.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S3DataExport.HowItWorks.html&quot;&gt;DynamoDB data export to Amazon S3: how it works&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/blogs/database/export-and-analyze-amazon-dynamodb-data-in-an-amazon-s3-data-lake-in-apache-parquet-format/&quot;&gt;Export and analyze Amazon DynamoDB data in an Amazon S3 data lake in Apache Parquet format&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think such a proposed solution can be even cheaper than the existing one (as AWS Kinesis is not cheap). Plus, even with DuckDB it may be less moving pieces. It also fits well with the current tech stack.&lt;/p&gt;
&lt;p&gt;Uber used &lt;a href=&quot;https://spark.apache.org/&quot;&gt;Apache Spark&lt;/a&gt; in their migration, and DuckDB is a modern alternative. So, there’s alignment here. See also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/datamindedbe/use-dbt-and-duckdb-instead-of-spark-in-data-pipelines-9063a31ea2b5&quot;&gt;Use dbt and Duckdb instead of Spark in data pipelines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;why-not-use-existing-databases-like-amazon-qldb&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-not-use-existing-databases-like-amazon-qldb&quot; aria-label=&quot;why not use existing databases like amazon qldb permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Not Use Existing Databases Like Amazon QLDB?&lt;/h2&gt;
&lt;p&gt;Well, obviously, because Amazon is sunsetting it. Jokes aside, why do they do it?&lt;/p&gt;
&lt;p&gt;While Amazon hasn’t publicly detailed all the reasons, we can speculate based on industry trends and challenges.&lt;/p&gt;
&lt;p&gt;Ledger databases are a niche market. The specialized nature of QLDB might have led to limited adoption compared to more general-purpose databases. Uber and Stripe’s decision shows that those niches also have specific needs that are hard to match with the general-built solution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why Amazon may be refocusing resources on services with higher demand and broader applicability, such as Amazon Aurora PostgreSQL.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Implementing and managing general usage ledger databases is extremelt complex. Customers might prefer more familiar relational databases with added features for auditing and compliance. Those who have strong legal needs may still come up with simpler solutions, as I described.&lt;/p&gt;
&lt;h2 id=&quot;why-build-your-own-database&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-build-your-own-database&quot; aria-label=&quot;why build your own database permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why build your own database?&lt;/h2&gt;
&lt;p&gt;Ledger databases offer significant benefits for applications that require an immutable and verifiable record of application behaviour. However, they come with tradeoffs in complexity, performance, and cost.&lt;/p&gt;
&lt;p&gt;The experiences of companies like Stripe and Uber demonstrate that sometimes building your own ledger system is necessary, especially when existing solutions don’t meet the specific needs of your domain, optimizing for specific use cases, and being prepared to tackle data integrity, system performance, and scalability challenges.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For discussed case, considering alternatives like using S3 with tools like DuckDB provides a more cost-effective and manageable solution while meeting regulatory requirements.&lt;/strong&gt; It simplifies the architecture, reduces costs, and leverages familiar AWS services and open-source tools.&lt;/p&gt;
&lt;p&gt;I think that the interesting observation for building your own solution can be on two spectrums:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Needing something more powerful:&lt;/strong&gt; like Stripe and Uber,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Needing something simpler and cheaper:&lt;/strong&gt; like in the proposal.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think this will also be a trend, and we’ll see more options like that as BLOB storage becomes more powerful and cheap. Building your own database layer might seem like a bold move, but sometimes, it’s the right one—especially when it ensures the longevity and integrity of your applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We should not be afraid to think about our own stuff; if we verify that:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;our solution has a limited number of features that won’t grow,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the competition doesn’t match our needs,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we have the team skills and capacity to run and maintain it,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the cost model is aligned with our needs,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;performance will be good enough.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those sound like famous last words, so we must be extremely careful. We can decrease the risk by using popular tools that will not disappear and will be relatively easy to replace, such as S3 and DuckDB, in our case.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are your thoughts?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you haven’t joined yet, here’s the &lt;a href=&quot;https://discord.gg/jEtvTFPy&quot;&gt;invitation link&lt;/a&gt; to our Discord community. Come on in and discuss your challenges with us!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s.&lt;/strong&gt; I’d like to know what’s interesting and what’s not. Your thoughts mean much to me, and I’d like to make this newsletter useful for you! I’d be grateful to fill out this short survey: &lt;a href=&quot;https://forms.gle/1k6MS1XBSqRjQu1h8&quot;&gt;https://forms.gle/1k6MS1XBSqRjQu1h8&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s.2. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Tech Debt doesn't exist, but trade-offs do]]></title><description><![CDATA[Welcome to the next week! I laughed hard right after entering the room. During the call, my friend Arek told someone: “We’ll add it to tech…]]></description><link>https://event-driven.io/en/tech-debt-doesnt-exist-but-trade/</link><guid isPermaLink="false">https://event-driven.io/en/tech-debt-doesnt-exist-but-trade/</guid><content:encoded>&lt;p&gt;Welcome to the next week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I laughed hard right after entering the room. During the call, my friend Arek told someone:&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;“We’ll add it to tech debt.”&lt;/strong&gt;&lt;/em&gt; It was years ago, and I probably heard that term for the first time. I was in my sarcastic heyday and thought it was a good joke. But Arek didn’t make a joke. He was serious about it being in the planning meeting.&lt;/p&gt;
&lt;p&gt;Technical debt. Reread it. It even sounds silly.&lt;/p&gt;
&lt;p&gt;It’s a term that gets tossed around a lot in our industry. We mention it in meetings, bring it up during code reviews, and use it to justify refactoring efforts. I believe that technical debt, as most people understand it, doesn’t really exist. It’s a convenient label that lets us acknowledge issues without actually addressing them. I’ll try to justify my bold claim in today’s edition.&lt;/p&gt;
&lt;h2 id=&quot;the-illusion-of-technical-debt&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-illusion-of-technical-debt&quot; aria-label=&quot;the illusion of technical debt permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Illusion of Technical Debt&lt;/h2&gt;
&lt;p&gt;We’ve all been there: tight deadlines, management pressure, and the temptation to cut corners to get something out the door. &lt;a href=&quot;http://c2.com/doc/oopsla92.html&quot;&gt;Ward Cunningham introduced it in 1992 at the OOPSLA conference&lt;/a&gt;. He said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Another, more serious pitfall is the failure to consolidate. Although immature code may work fine and be completely acceptable to the customer, excess quantities will make a program unmasterable, leading to extreme specialization of programmers and finally an inflexible product. Shipping first time code is like going into debt. &lt;strong&gt;A little debt speeds development so long as it is paid back promptly with a rewrite.&lt;/strong&gt; Objects make the cost of this transaction tolerable. &lt;strong&gt;The danger occurs when the debt is not repaid.&lt;/strong&gt; Every minute spent on not-quite-right code counts as interest on that debt. Entire engineering organizations can be brought to a stand-still under the debt load of an unconsolidated implementation, object- oriented or otherwise.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The classic idea of technical debt suggests that we can take a shortcut now and “pay it back” later, like borrowing time from the future. But once code is written and shipped, it’s part of our reality. There’s no magical account where we can deposit future work to settle the debt.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=WPjXPRNQopo&quot;&gt;Lannisters always pay their debts&lt;/a&gt;, and developers also do, right?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 503px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/39eaeee62be67a8971269bebd5dde150/b6798/2024-11-04-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 99%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGQABAQEAAwAAAAAAAAAAAAAAAAMBAgQF/8QAFwEAAwEAAAAAAAAAAAAAAAAAAAIDAf/aAAwDAQACEAMQAAABjs6zbqtBx9EbJCR//8QAGhAAAgMBAQAAAAAAAAAAAAAAAQIAAxESMf/aAAgBAQABBQIvjs2wswOkHyGxtsMrUPXws//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABcRAAMBAAAAAAAAAAAAAAAAAAABEDH/2gAIAQIBAT8Br0//xAAbEAACAgMBAAAAAAAAAAAAAAAAAQIxEBJhcf/aAAgBAQAGPwKzhY/RRiqwyOxR/8QAHRABAQEAAgIDAAAAAAAAAAAAAREAIUFRYXGBwf/aAAgBAQABPyFuVzxkgvyySLIXpXWoYALjQg/WW+rmblT9zR4b/9oADAMBAAIAAwAAABAY/wD9/8QAGBEBAQADAAAAAAAAAAAAAAAAAAERQYH/2gAIAQMBAT8QkrjUZf/EABkRAQACAwAAAAAAAAAAAAAAAAEAEBEhMf/aAAgBAgEBPxDBqgJ2n//EABwQAQADAAMBAQAAAAAAAAAAAAEAESExQWFxUf/aAAgBAQABPxBGWymmj7OXlRAuvDpDKq7ysAliCApL9j15Q0RX9fsR0jC9VL9ZdRnrDEVY01oiE2t72f/Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/39eaeee62be67a8971269bebd5dde150/507e6/2024-11-04-cover.webp 200w,
/static/39eaeee62be67a8971269bebd5dde150/28a80/2024-11-04-cover.webp 400w,
/static/39eaeee62be67a8971269bebd5dde150/43573/2024-11-04-cover.webp 503w&quot;
              sizes=&quot;(max-width: 503px) 100vw, 503px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/39eaeee62be67a8971269bebd5dde150/37402/2024-11-04-cover.jpg 200w,
/static/39eaeee62be67a8971269bebd5dde150/4cda9/2024-11-04-cover.jpg 400w,
/static/39eaeee62be67a8971269bebd5dde150/b6798/2024-11-04-cover.jpg 503w&quot;
            sizes=&quot;(max-width: 503px) 100vw, 503px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/39eaeee62be67a8971269bebd5dde150/b6798/2024-11-04-cover.jpg&quot;
            alt=&quot;2024 11 04 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Imagine a team pushing hard to deliver a new feature. To meet the deadline, they skip writing unit tests, promising themselves they’ll add them later. The feature ships on time, but the tests never get written as months pass. Is it an issue?&lt;/p&gt;
&lt;p&gt;Some can say, &lt;em&gt;“sure: it’s a tech debt”&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But why is it an issue?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Or giving you a hint: &lt;strong&gt;when will it become an issue?“.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cutting quality corners will obviously increase the likelihood of bugs and security issues. When we surface them without tests, we’ll have a harder time diagnosing and fixing them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But if I told you that the team were:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;building a proof of concept,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;delivering an internal tool supporting the process,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;setting up a one-time user reporting tool,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;bumping dependencies in the legacy system,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;doing the first change since a few years ago to the old system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Would that change the perspective? Would you still call it a tech debt?&lt;/p&gt;
&lt;p&gt;The quality corners could have the same scale, but the consequences would be more or less severe. Tech debt is not an issue by itself; the consequences of our decisions can become an issue.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;By labelling the absence of tests as technical debt, we postpone the real issue.&lt;/strong&gt; We do not acknowledge that we made a trade-off: speed over quality. The term becomes a crutch, allowing us to feel okay about not addressing the underlying problem. It’s an easy excuse.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/52d215e22819d072f149d713c1e8a140/image-2.gif&quot; alt=&quot;a man is holding a fire extinguisher and saying &amp;#x22; i &amp;#x27;ll just put this over here &amp;#x22; with the rest of the fire&quot; title=&quot;a man is holding a fire extinguisher and saying &amp;#x22; i &amp;#x27;ll just put this over here &amp;#x22; with the rest of the fire&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“I’ll just put this over here, with the rest of the fire”.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I’ve seen that so many times, and I’m fed up with that narrative.&lt;/p&gt;
&lt;p&gt;We know where tech debt lands, in the dark place called the end of the Jira backlog.&lt;/p&gt;
&lt;h2 id=&quot;trade-offs-the-reality-of-software-development&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#trade-offs-the-reality-of-software-development&quot; aria-label=&quot;trade offs the reality of software development permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Trade-Offs: The Reality of Software Development&lt;/h2&gt;
&lt;p&gt;Software development is a series of trade-offs. Every decision we make has consequences, some immediate and some that manifest over time. Framing these decisions as “technical debt” oversimplifies the complex reality and obscures our real choices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When discussing the significance of tech debt, I often hear that the maintenance cost becomes bigger if we have lower quality.&lt;/strong&gt; I wrote in &lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;Removability over Maintainability&lt;/a&gt; that maintainability is pictured as an ultimate goal in our industry.&lt;/p&gt;
&lt;p&gt;Let me tell you something: &lt;strong&gt;Your system may never reach the phase where maintainability is essential.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;How come? Unfortunately, most of our systems are either moderately successful or not at all. On my own, I worked on systems that were built for a few years without even having real production usage. Unfortunately, waste of time is a big issue in our work. We’re not wasting time coding too slowly; we’re wasting time doing stuff that doesn’t matter. Also, regarding maintaining code, it should have just been removed or the systems rewritten.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not many of us want to work in legacy systems.&lt;/strong&gt; They look to us as a bad rap. They’re seen as outdated, cumbersome, and in need of replacement. But these systems have been battle-tested. They’ve evolved over time to handle real-world scenarios that new systems might not anticipate.&lt;/p&gt;
&lt;p&gt;Sometimes, we’re disgusted by the 10-year-old code written by numerous people. We don’t want to touch those obsolete tech stacks. We say that the tech debt is so high that we need to rewrite it, most of the time, keeping the whole system in mind. When our management asks us for justification, we try to repeat this smartly sounding statement. Then, we’re frustrated with the brutal reality check. Management says no. Why?&lt;/p&gt;
&lt;h2 id=&quot;trade-off-analysis-checklist&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#trade-off-analysis-checklist&quot; aria-label=&quot;trade off analysis checklist permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Trade-off Analysis Checklist&lt;/h2&gt;
&lt;p&gt;Too often, we don’t do our homework and check if the old code and old tech stack are an issue on their own. We just want to make the change to make our life easier. But the sad truth is that it’s not an argument per se.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What do I mean by homework?&lt;/strong&gt; &lt;strong&gt;For instance, answering the following checklist:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;How often does that code change?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When was the last time someone touched this code?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How long did it take to make the last change?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How long do I expect my new change to take?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is there a big difference between those delivery times?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Why is it big?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Can someone else with higher familiarity do it?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Can we do it together?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What’s the actual cost of change?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What worse can happen if I make a bug in this code?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How much can this mistake cost?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What are the other potential issues?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What’s the likelihood of them happening?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are there features with similar issues?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How do we solve them?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Can we solve this case differently?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What’s the fastest way to solve this use case?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What’s the cheapest way to solve it?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Can we not make this change?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Would it be cheaper to pay some employees to do this task manually?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What’s the impact if we do this change?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What’s the impact if we don’t do this change?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;How do we measure it?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the real analysis and questions we should ask ourselves, our business, and our colleagues. By gathering answers, we can make an educated decision with proper justification.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now, we’re talking!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That’s what we can hear from the business after providing the outcomes of this analysis instead of blank statements like:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 576px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/00c1cbaa838266f4f31e884e424bcb47/7fee0/image-3.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABAABAv/EABYBAQEBAAAAAAAAAAAAAAAAAAIBBP/aAAwDAQACEAMQAAABEkSHOLbQP//EABsQAAIBBQAAAAAAAAAAAAAAAAECIQASEyIx/9oACAEBAAEFAkc1sFMnHNrFV5//xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAwEBPwEn/8QAGBEAAgMAAAAAAAAAAAAAAAAAAAECMUH/2gAIAQIBAT8Bk8FR/8QAHBAAAQMFAAAAAAAAAAAAAAAAAQAQIRESMkFx/9oACAEBAAY/Ao0utlRAXiJb/8QAHBAAAgMAAwEAAAAAAAAAAAAAAREAITFBYXHR/9oACAEBAAE/IcBkDxGCebOMw7AmNsMVKe75LXuf/9oADAMBAAIAAwAAABDHP//EABYRAQEBAAAAAAAAAAAAAAAAAAABUf/aAAgBAwEBPxDSv//EABcRAQEBAQAAAAAAAAAAAAAAAAEAESH/2gAIAQIBAT8QQxHRsIS//8QAGxABAQADAQEBAAAAAAAAAAAAAREAITFRQWH/2gAIAQEAAT8Qew4iNFXpq5frSNeByTLT1dcegB4Xer8HBBYL8/r+B+euUSqFF93n/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/00c1cbaa838266f4f31e884e424bcb47/507e6/image-3.webp 200w,
/static/00c1cbaa838266f4f31e884e424bcb47/28a80/image-3.webp 400w,
/static/00c1cbaa838266f4f31e884e424bcb47/c4eb6/image-3.webp 576w&quot;
              sizes=&quot;(max-width: 576px) 100vw, 576px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/00c1cbaa838266f4f31e884e424bcb47/37402/image-3.jpg 200w,
/static/00c1cbaa838266f4f31e884e424bcb47/4cda9/image-3.jpg 400w,
/static/00c1cbaa838266f4f31e884e424bcb47/7fee0/image-3.jpg 576w&quot;
            sizes=&quot;(max-width: 576px) 100vw, 576px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/00c1cbaa838266f4f31e884e424bcb47/7fee0/image-3.jpg&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;cost-of-change&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cost-of-change&quot; aria-label=&quot;cost of change permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cost of change&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Ward Cunningham introduced the term “technical debt” to help explain to non-technical stakeholders the cost of incomplete or immature code.&lt;/strong&gt; It was meant as a metaphor, not an accounting practice. But when we start treating it as a quantifiable debt that can be managed like financial debt, we run into problems.&lt;/p&gt;
&lt;p&gt;Unlike financial debt, code doesn’t get worse on its own over time. It doesn’t accrue interest. The challenges arise when we need to change or extend that code. The difficulty isn’t a function of time but of context—new requirements, different team members, evolved understanding.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And hey, Ward Cunningham coined this term in 1992!&lt;/strong&gt; 32 years ago. I was 7 years old at that time. &lt;a href=&quot;https://www.youtube.com/watch?v=zDKO6XYXioc&amp;#x26;list=PLYosk6VjN4iZ1mytCFb0ON350G3YT70Fp&amp;#x26;index=1&quot;&gt;Boyz II Men’s “End Of The Road”&lt;/a&gt; was the hottest musical single. That didn’t stand the mark of time, but technical debt did?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most of the projects at that time were digital transformation and automatisation.&lt;/strong&gt; We had a clear split between business and IT. At that time, this metaphor could sound good enough to bridge the understanding. But now IT is a business, we should find a better language to discuss our issues.&lt;/p&gt;
&lt;p&gt;A legacy system might be labelled as technical debt because it’s hard to work with. But that system might be stable, reliable, and fulfilling its original purpose well. The difficulty comes when adapting it to new needs without fully understanding its intricacies. We should not reason based on our gut feeling or any feeling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It may appear that:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;it’s cheaper to write a procedure that someone manually performs the task,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;cancel the feature and do it differently,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;do a one-time update, even if the cost of change is high, but it’s happening once per year, and the rewrite cost will be much higher.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;It may also appear that we need to rewrite the whole feature instead of adding yet another checkbox.&lt;/strong&gt; That happened to me. I was working on the employee management system. I was responsible for the module handling the legal part of hiring and handling employment. I had to add one more checkbox. Yet, it appeared that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;documentation was sparse and distributed all around,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I didn’t find any of the original developers that created it,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I didn’t find anyone from the current employee who would explain to me the process I was responsible for,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I did my notes on understanding and potential consequences. No one from the business could confirm or deny if I understood that correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The risk of wrong change and legal consequences were so severe that we couldn’t afford the risk of making it wrong.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;I explained that I could make the “simple”, literal change but then could not take responsibility for the consequences.&lt;/strong&gt; Businesses also didn’t want to take responsibility for that. I also presented alternatives with the focused refactoring with the minimum scope. I outlined the plan, starting from the analysis and working closely with the business to validate my assumptions as I got. I also worked for some time in the business, which earned their trust. I let them decide, and they chose wisely to do this refactor.&lt;/p&gt;
&lt;p&gt;It was successful, but that was not because we reduced tech debt. It was business motivation backed by precise analysis and cooperation in weighing the trade-offs.&lt;/p&gt;
&lt;p&gt;We should acknowledge the value of the existing system and then choose to refactor and modernise parts of it incrementally rather than replace it wholesale. I haven’t ever seen a successful rewrite that would try to design a new system 1:1.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We should always focus on the &lt;a href=&quot;https://en.wikipedia.org/wiki/Pareto_principle&quot;&gt;Pareto Principle&lt;/a&gt; and try to find the 20% of features that give 80% of the business value.&lt;/strong&gt; That should be our priority. Not the vague technical debt.&lt;/p&gt;
&lt;h2 id=&quot;the-sunk-cost-fallacy&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-sunk-cost-fallacy&quot; aria-label=&quot;the sunk cost fallacy permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Sunk Cost Fallacy&lt;/h2&gt;
&lt;p&gt;Seth Godin in his article &lt;a href=&quot;https://seths.blog/2009/05/ignore-sunk-costs/&quot;&gt;”Ignore Sunk Costs”&lt;/a&gt;, wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You have tickets to the Springsteen concert. They were really hard to get. You spent four hours surfing StubHub until you found the perfect seats for $55 each.&lt;/p&gt;
&lt;p&gt;On your way into the event, a guy offers you $500 cash for each ticket. Should you sell?&lt;/p&gt;
&lt;p&gt;It turns out the amount of time you spent getting the tickets is irrelevant. If you wouldn’t be willing to PAY $500 for these tickets (and you weren’t, or you would have) then you should be willing to sell them for $500. Spend $250 on dinner and go buy better tickets for tomorrow night’s show.&lt;/p&gt;
&lt;p&gt;Or say you make a mistake and go to the concert instead of selling (those seats are $500 seats now). But Bruce is sick and Manfred Mann is substituting for him. You don’t like him so much. But you paid $500 for the seats! Should you stay?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The sunk cost fallacy occurs when we continue investing in a decision based on the cumulative prior investment—time, money, or effort—even when the current costs outweigh the benefits.&lt;/p&gt;
&lt;p&gt;Seth wrote that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When making a choice between two options, only consider what’s going to happen in the future, not which investments you’ve made in the past. The past investments are over, lost, gone forever. They are irrelevant to the future.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;That’s also why tech debt doesn’t exist.&lt;/strong&gt; There’s simply no such thing. It’s virtual.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You have the current state of the art. The rest is just sunk cost fallacy: your memory of what you did before.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Understanding the past context is essential; that’s why we should do &lt;a href=&quot;https://www.cognitect.com/blog/2011/11/15/documenting-architecture-decisions&quot;&gt;Architecture Decision Records&lt;/a&gt;. Yet, we should make decisions based on the current state because that’s our reality.&lt;/p&gt;
&lt;p&gt;Measuring virtual debt is a waste of time. We should not focus on categorising the past and measuring it. We should measure and optimise the real impact, such as delivery speed, outcomes, etc.&lt;/p&gt;
&lt;p&gt;Usually, tech debt is some backlog and an easy excuse for people that they’re doing something with low-quality code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And what does “low-quality code” even mean?&lt;/strong&gt; You can have terrible code; if there’s no need to change it, it doesn’t negatively affect delivery.&lt;/p&gt;
&lt;p&gt;Talking about tech debt is a surrogate conversation for me, which allows us to feel good because “we’re moving” and not talk specifically about what’s wrong with our process.&lt;/p&gt;
&lt;p&gt;But if our process is flawed, we should improve it, not because we have “technological debt” but because, for example, we deliver slower or of lower quality, which translates into finances.&lt;/p&gt;
&lt;p&gt;I’m happy that’s changing, and more and more projects are writing down their design decisions. But the main problem still remains. We don’t explain alternative choices we discarded, and most importantly, &lt;strong&gt;we don’t include the “expiration date” or “expiration parameters” in our decision.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For instance, we could write:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I assume that if we exceed the assumed traffic by X and that Y, it will be necessary to rewrite/change the technology, let’s evaluate it when we get close to such a X or Y threshold.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Thanks to that, we’re giving our future selves hints on when to evaluate our past assumptions. Decisions are never good or bad, but the outcomes of the decisions can be such.&lt;/p&gt;
&lt;p&gt;Of course, we can also make a mistake. Writing things down if not a full metal jacket. It won’t protect us, but at least we can have a reasonable discussion with specific data about what we should do and not about tech debt—that is, about what we didn’t do.&lt;/p&gt;
&lt;h2 id=&quot;communication-with-stakeholders&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#communication-with-stakeholders&quot; aria-label=&quot;communication with stakeholders permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Communication with Stakeholders&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Simply stating that it is necessary to address technical debt doesn’t resonate with non-technical stakeholders.&lt;/strong&gt; They care about outcomes—performance, reliability, and user satisfaction—not the cleanliness of the code.&lt;/p&gt;
&lt;p&gt;Instead of saying:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We need to refactor this module because of technical debt.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Frame it in terms of impact:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Improving this feature will reduce page load times by 50%, enhancing user experience and increasing retention.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;By optimising our database queries, we can save on infrastructure costs and improve scalability.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;One can say, “but how do I know that?!”.&lt;/strong&gt; Only by talking with your peers and business people and doing homework. Saying that “we have technical debt” is an excuse. No excuses; be accountable. No one said that’s going to be easy. That’s why we’re here for. Of course, that requires our effort to quantify benefits. We may gather data and metrics to back up claims. But…&lt;/p&gt;
&lt;p&gt;Thanks to that, stakeholders see the direct value. You’re becoming a partner for them, as you’re providing them with data to make decisions. You’re taking the burden out of them, joining your technical expertise with the business goals. That’s what we discussed with Gojko Adzic in the webinar:&lt;/p&gt;
&lt;p&gt;[&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 56px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC80lEQVR42o2VO0hbYRTHr4nvJ6VK1fiig7YIobo4ZDAWEgfpIg4+0aWDQ8DFki0FFReLLipOIopgfEDRRR0FBxdRR4cKVXAQI4nGmHu9/55zki9VWmM+OPnuvd/5fuf/Pc6JBsCiUbu8vHz38PDw1TAMbzQaHab+WzIjH9UPB4NBBzP8fr+Vey0UCtlp8BdizUTqzVT+JOa7phrB9uMOETI9HA7r3CezSCSinqOPj48GTyZhn7Wbm5v3BAyqiB6PBwMDA/KytbWFi4uL2IAZE06+0i8uLsLpdOLq6gpxMO7v739o1NfRPoT4w8bGhkmC0d3dLZOampqwvb39DKTregLIvkNDQzLMP7e3tzMaSa+j9d/xh/HxcQH29/fLpMHBQezvx3aDovM+4e5OXLGwsCBAl8uVUEhb9X9gb2/vM1VPm1I4Pz8vwLa2tpeBo6OjZnp6OmpqatDZ2SlgVsvPHR0d8tzX1ye93W5HdnY2WltbXwaOjIyYmZmZKC8vF2c2VsEB6uvrYbFYwONZWVkoLi5GQUEB3G53cmBGRgYqKytRXV2NoqIiTE5O4vT0FIeHh9jd3UVFRYUY++Tn578OZAUMy8nJQVdXF46Pj9HY2Ija2losLy9jenoavC1VVVWpA3mJvNTZ2Vl4vV6MjY3h6OgIDocDe3t7slRW+SqQD+Wpwp6eHlkqHwArXFpawtzcHHhblMJXD4UPgqOrg5mYmMDJyYko29nZQWlpKUpKSmCz2URp0ns4NTUl95CjM1Atq6GhAc3NzQIoKysTGB8K72V7e/s/wA90gSX1rq+vzZaWFlHGILbCwkJYrVakpaXJO0Pz8vKQm5srgQ8ODhKpR6wZjZLbRtUioIoDFQtsbm5iZWUFq6urYmtra1hfX0+8Kzs7O1MZpceBPlW+/PyBwDpVFSMeMSUz4vlJc8ME/ChAUvWGKs5P42/yplpkTS5rNO035fgXgfl8Pgv/DbAFAoFPNMAXy5WiuakKOc/Pz9+qv4A/x/GAJi+lEOYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/61627/image-4.webp 56w&quot;
              sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-4.png 56w&quot;
            sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-4.png&quot;
            alt=&quot;image 4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;Architecture Weekly&lt;/p&gt;
&lt;p&gt;Webinar #23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/p&gt;
&lt;p&gt;Welcome to the next week…&lt;/p&gt;
&lt;p&gt;Listen now&lt;/p&gt;
&lt;p&gt;2 years ago · 4 likes · Oskar Dudycz and Gojko Adzic&lt;/p&gt;
&lt;p&gt;](/en/webinar-23-gojko-adzic-on-designing/)&lt;/p&gt;
&lt;p&gt;Effective communication bridges the gap between technical teams and business stakeholders, ensuring everyone is aligned on priorities.&lt;/p&gt;
&lt;h2 id=&quot;continuous-improvement&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#continuous-improvement&quot; aria-label=&quot;continuous improvement permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Continuous Improvement&lt;/h2&gt;
&lt;p&gt;We should integrate improvement efforts into our regular workflow rather than deferring work under the tech debt banner.&lt;/p&gt;
&lt;p&gt;Do you want to make a “refactoring sprint”? Or maybe make “zero sprint for architecture”?&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 324px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/3e6ac6b9a3d52128d3c7a5dffcac3506/a339b/image-5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 145%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAdCAYAAACqhkzFAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHiUlEQVR42iXV+VPa+R3Hcf6N/tKZdvpDd6czOzvdTtpNskmniZO4mngiiigeaPA+EBBBvFBABBRFxQsQ8dZojEYNXkmM5jLXxmTbdJtkp53tsel02+42befZj/SH93x+AF6f9/v7/XweSBTpCaiUUgpy0ihWZaKvLsKgOSo1RQXZZKZLyc/NRKlIJzkxmaSL/6+0FBnydAWZGQpSElOIi43n5IkTSIpz0qkqykVfqUZTmk+9vpwGQyWNdZUUX8olJSWJgvxccpXZyGVSYs+dI10qQyGC0mUZpKfJRWWSmpwmPotDoinJw6Qto6W+hmaTBmujnjZRtmYDlcX5xJ85jvTT0+TL4qjISUGVJSo3nTxlltgkl2x5NrLUDKTJMuJFlxKTtlQEVWNt0tPaoIsGtpq11NdWkBR3hnPHP6A49Syrox5m+qxo8tJQq5SoCwuoLCunqLBIPBIVCnkWiRcSkDSIES3mGhytJlH1OK1m3PbG6OhtzXWo5InolIk83bzMrUU/OpWU6opidNo6jAYTmsoaKsuryc9RiUeRgeSou6Og7g4rXreNvi4Hg95OKtS52Bq1JIqRM8+fxFlXQl2BVFQ6LqeDtg43FruDplYb5qZWDHX1IrgcibOtnoEeN8FBH8EhH+Ggn9ER0UllOaV54oUpLpAZc4yexiqu+jtx1RbjGxzC6RvC4e2jo8+Htasbe5cHc2MjkqHeLsZGhhkfHWMiFGZqfILlpWvcu3NXdG7m3LH3STjxAaNOM4uDDvw9oibm8AbH8QbC9ATGcQ+M4PINU9fShiQ4EmAyHGZmak6ETbEZ2RZhe6zOh9AXKzn2/g+JO/EhLWVKbNWFTM7M0yF+7BVB4YU1ppd3GJ5aomtkAqPNjaTb7SE4EmR+dpHPnh7y7d+/ZmdplLz4k9g0KpLPfkzMRz/GWJDG7bV5nhzsE56dZWB8gZnlGyxc3yc4v4p7eAK9rRPJ8ICfrcgGf/7TH3j33Vu+fvWEz3eXCLSb8NsNvDi4Sb/TwtKMn7+8ec7L/WW+PNzlzv4uE4sRpkSHfeHLeALT1LX3IAmNjjI76hVf2uPN421ePVjn8/0VPttZ4GFkFv77LQf7OxzsbXF4d5tnO/M83JzmIDLF5OwcvaHLdAyMYesNoG0VI9uaTdTkJDPZ1cLhjSt8cW+Ne6vj7K+M8WL3Cm+/eklbbRldxmoOH+xw79oYd69PsrM4RGjIS5s3gMUzhNbmoaLBjkRbWoBGmYTPouPg+ixfPtoSnc3wSHT35uA6D7fmyDj9U5qz0thcDHF3eZS9lRCbs/2EBzqx9wVo9AyitXvQWl1IlPJk1DlSasQbbTaU4baasDfrcbbqcTRraDNXUio0KslMoSRfHj3wxfnZQiZRhXkUqkvIVKpITc8iPiEZSaEyjbLCbCGNinJ1nrj4WUhTkgVLR5KIVS5DmaNEliG4kiuIjb3ARcFXoiArOTGV5IQUUsWaeCGRmF/FIKkRQYaqIiwmoY0Qp8moQ1NeiqailPTUi8Sc+jmyC2epFlZWqORkZySRIRiTSVORpaUiTZWSIQg74itObCYx1pQI+6qEMrWCLV2ULnuLCZO+jFxFMhnxp8ShVvBsc4Gp7hbKBGFFl/IoUedTXlJMoUotYMgTVspFl2LkBsHUkYVH2risDUKbBty2JoFsFa72ZlJijtNYJGdrZpiBVtF9vgxdTTUGnYFafR3VlVrKSsujNh5hK7GYddE72+1spa+znX6PE1+3i/JCJXUVhWQIabJiT9ChvYQhLwWrthiXu4tWRwctNgeNbXbqmywYjWZKisqQOCwN0YDgUB/B4UHGAgFCfj8tDfWUCpmVcZ+Qff44a2NetmcH6bYY6A+GcPQN4OjtF9r0Y+v2YhObaHR1SHq7OkVAkPCRNmOTYh1n5eoajx89jv63fPze95H+8iOcejWdxlImxsMMhKejOPSOTtHjH6Nz0I+tpx9NveBruH9AGHjE1jQzk7Pc2bvDnd0teu0mKnOlfPiD75F2+iOMuUmEe53MLK3Q5umlPySu3tIWU1e3hTYLuAaCaMwtSLyeHkKBEOurEV7/7g3//u4b9tenKZedo0cc6tqibBJ+8RNaq1S8PrzPi8e3WduMCGGuMbNyk/nVXUZmlmn3jaK1OJEEhgJE1jf429s/8q9/vuWrlw/5rbjPkYl+IlODvPvHX4msXBZs3eL3vzkQ2qzw5tkuuzc3mbiyQXBulb6xWVxD4+jbBF/+IT8dLQa2FwJ8cbAhtInwTCDxdOsyz28t8Z933zAXGmLjyiRP9lY5FArdWx/n1nKIkbEJ3INh2sV9Nnf0obN6kNjbWqhUJuCpL+f+2iSvDiJinWJ3cZRf377K8wcbKD89xaDFyKPbq+xdDUYlWp/sw+frxer10+jqj4ZpLB1IrE1GqnMSGbbVcmN+mFf3r/NkW8i8OctrYeOe0Ft5+mc0KaRM+xzcvzbBLTHNStDFiK+bDjGq2eWLVs1RoEKWRNr5T7Bo1fTb6+m1GhlxN9HVrGVu2MXkgAN1/Bli3/sRRZlJ+N2W6FlsqVZh1ldRJ0LKahu5pDEhVxXxP0Ba5sjfChozAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/3e6ac6b9a3d52128d3c7a5dffcac3506/507e6/image-5.webp 200w,
/static/3e6ac6b9a3d52128d3c7a5dffcac3506/d8cf6/image-5.webp 324w&quot;
              sizes=&quot;(max-width: 324px) 100vw, 324px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/3e6ac6b9a3d52128d3c7a5dffcac3506/36ca5/image-5.png 200w,
/static/3e6ac6b9a3d52128d3c7a5dffcac3506/a339b/image-5.png 324w&quot;
            sizes=&quot;(max-width: 324px) 100vw, 324px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/3e6ac6b9a3d52128d3c7a5dffcac3506/a339b/image-5.png&quot;
            alt=&quot;Stop it, Get some help,” and why the meme will live on. | by ...&quot;
            title=&quot;Stop it, Get some help,” and why the meme will live on. | by ...&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We should try to make small, consistent improvements as part of everyday development. This avoids the need for massive overhauls later and keeps the codebase healthier over time. Those small, precise, and consistent changes will stack and gradually build your culture and have a big impact. Check out &lt;a href=&quot;https://jamesclear.com/atomic-habits&quot;&gt;James Clear’s “Atomic Habits” book&lt;/a&gt;; it will help you with that.&lt;/p&gt;
&lt;p&gt;Always keep business value in mind. Focus on where it matters. Work smart, not hard. As I mentioned earlier, this will help you build trust with business people.&lt;/p&gt;
&lt;h3 id=&quot;tech-debt-is-dead-it-really-is&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tech-debt-is-dead-it-really-is&quot; aria-label=&quot;tech debt is dead it really is permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Tech debt is dead; it really is&lt;/h3&gt;
&lt;p&gt;Let’s check how &lt;a href=&quot;http://wiki.c2.com/?WardExplainsDebtMetaphor&quot;&gt;Ward Cunningham explained his tech debt metaphor later on&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A lot of bloggers at least have explained the debt metaphor and confused it, I think, with the idea that you could write code poorly with the intention of doing a good job later and thinking that that was the primary source of debt.&lt;/p&gt;
&lt;p&gt;I’m never in favor of writing code poorly, but I am in favor of writing code to reflect your current understanding of a problem even if that understanding is partial.&lt;/p&gt;
&lt;p&gt;You know, if you want to be able to go into debt that way by developing software that you don’t completely understand, you are wise to make that software reflect your understanding as best as you can, so that when it does come time to refactor, it’s clear what you were thinking when you wrote it, making it easier to refactor it into what your current thinking is now.&lt;/p&gt;
&lt;p&gt;In other words, the whole debt metaphor, let’s say, the ability to pay back debt, and make the debt metaphor work for your advantage depends upon your writing code that is clean enough to be able to refactor as you come to understand your problem.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, in other words, tech debt wasn’t meant to be a rotten compromise that we do to deliver faster with lower quality. It was about delivering based on our best understanding of the current situation. That view may be flawed, but we should embrace it.&lt;/p&gt;
&lt;p&gt;Still, the focus is on the future. We only see the outcome of our past choices if we made them; what’s more, they will be unrelated if we don’t need to make another decision based on them.&lt;/p&gt;
&lt;p&gt;So please, please, please…&lt;/p&gt;
&lt;p&gt;Stop using this bloated tech debt metaphor.&lt;/p&gt;
&lt;p&gt;We should do our homework. Accept the current state, ignore the sunk cost, and improve our design by properly analysing and showing adequate business arguments to your peers.&lt;/p&gt;
&lt;p&gt;There’s no easy fix or magic formula. It requires ongoing effort, clear and direct communication, and a willingness to confront challenges. But by doing so, we build better software and deliver value.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s Your Take?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I’d love to hear your experiences and thoughts on this topic. Have you found the concept of technical debt helpful or hindering in your projects? How do you approach making trade-offs and prioritising work?&lt;/p&gt;
&lt;p&gt;Check also my other articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;Removability over Maintainability&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/follow_the_money/&quot;&gt;Follow the money to get a better design&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/why_are_we_afraid_of_our_decisions/&quot;&gt;Why are we afraid of our decisions?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/why_are_we_afraid_of_our_decisions/&quot;&gt;The risk of ignoring risks&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/chesterton_fence_and_software_architecture&quot;&gt;What do the British writer and his fence have to do with Software Architecture?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/a_few_words_on_communication/&quot;&gt;A few words on communication&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. I’d like to know what’s interesting and what’s not.&lt;/strong&gt; Your thoughts mean much to me, and I’d like to make this newsletter useful for you! I’d be grateful to fill out this short survey: &lt;a href=&quot;https://forms.gle/1k6MS1XBSqRjQu1h8&quot;&gt;https://forms.gle/1k6MS1XBSqRjQu1h8&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s.2. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Frontent Architecture, Backend Architecture or just Architecture? With Tomasz Ducin]]></title><description><![CDATA[Welcome to the new week! For a long time, I’ve wanted to talk more about Frontend Architecture. In my projects, I always enjoyed delivering…]]></description><link>https://event-driven.io/en/frontent-architecture-backend-architecture/</link><guid isPermaLink="false">https://event-driven.io/en/frontent-architecture-backend-architecture/</guid><content:encoded>&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Webinar recording&quot; src=&quot;https://www.youtube-nocookie.com/embed/EXj9TTJQwNc?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For a long time, I’ve wanted to talk more about Frontend Architecture. In my projects, I always enjoyed delivering features end-to-end the most.&lt;/strong&gt; That gave me the feeling of completing and delivering something useful for the user. There were times when I just did backend, times when I was only doing frontend, and most of the time, working full-stack.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For many years, the front end was treated as a side effect of the backend processes.&lt;/strong&gt; Probably because initial systems were mostly about automatic processes, document workflows or calculations. Nowadays, we have more user-facing applications. Fluent User Experience and nice User Interface are values of their own. Single-page applications emerged together with frameworks like React, Angular, Vue, etc., changing the scene of our systems. More and more responsibility was pushed to the front end from the backend. Frontend applications had to be standardised and stopped being just the tip of the iceberg for many systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why I wanted to discuss Frontend Architecture, and I’m happy that our special guest, &lt;a href=&quot;https://ducin.dev/&quot;&gt;Tomasz Ducin&lt;/a&gt;, agreed to discuss the current stat of the art complexity, where to draw lines, and all that jazz.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/cdc565ac98f7ffb7dc2dc68ef516fc54/2934e/image-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAMEBQH/xAAWAQEBAQAAAAAAAAAAAAAAAAABAAL/2gAMAwEAAhADEAAAAc+3HYHPTkr9NEgr/8QAGxAAAgMBAQEAAAAAAAAAAAAAAQIAAxIRISL/2gAIAQEAAQUCRdNbRgQfIezq5jeuJ0if/8QAFREBAQAAAAAAAAAAAAAAAAAAIEH/2gAIAQMBAT8Bg//EABURAQEAAAAAAAAAAAAAAAAAACBB/9oACAECAQE/AaP/xAAZEAEAAwEBAAAAAAAAAAAAAAABABARAhL/2gAIAQEABj8CCaO16Yc7Tf8A/8QAHBAAAgICAwAAAAAAAAAAAAAAAAERIRBBMVFh/9oACAEBAAE/IbQidil+oQQqK7FMEvkdh3I1SwVRM//aAAwDAQACAAMAAAAQBCgA/8QAFhEBAQEAAAAAAAAAAAAAAAAAEQEQ/9oACAEDAQE/EIJy5//EABcRAQEBAQAAAAAAAAAAAAAAABEAARD/2gAIAQIBAT8Q1BFnP//EABsQAQADAQEBAQAAAAAAAAAAAAEAESFBUWGR/9oACAEBAAE/EMtB1bkSqBovI1hMgN118hxOGgH7K8aQQdjo8IBYBiPJ/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/cdc565ac98f7ffb7dc2dc68ef516fc54/507e6/image-2.webp 200w,
/static/cdc565ac98f7ffb7dc2dc68ef516fc54/28a80/image-2.webp 400w,
/static/cdc565ac98f7ffb7dc2dc68ef516fc54/8d2ea/image-2.webp 800w,
/static/cdc565ac98f7ffb7dc2dc68ef516fc54/68fc1/image-2.webp 1200w,
/static/cdc565ac98f7ffb7dc2dc68ef516fc54/43d96/image-2.webp 1600w,
/static/cdc565ac98f7ffb7dc2dc68ef516fc54/c2731/image-2.webp 2105w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/cdc565ac98f7ffb7dc2dc68ef516fc54/37402/image-2.jpg 200w,
/static/cdc565ac98f7ffb7dc2dc68ef516fc54/4cda9/image-2.jpg 400w,
/static/cdc565ac98f7ffb7dc2dc68ef516fc54/c60e9/image-2.jpg 800w,
/static/cdc565ac98f7ffb7dc2dc68ef516fc54/6c738/image-2.jpg 1200w,
/static/cdc565ac98f7ffb7dc2dc68ef516fc54/56dca/image-2.jpg 1600w,
/static/cdc565ac98f7ffb7dc2dc68ef516fc54/2934e/image-2.jpg 2105w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/cdc565ac98f7ffb7dc2dc68ef516fc54/c60e9/image-2.jpg&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I wanted to invite Tomasz for a long time, as he’s one of the best people I know, specialising in Frontend and Architecture.&lt;/strong&gt; He’s Independent Consultant, Architect, Developer, Speaker, Trainer. Expertise in Web Technologies &amp;#x26; Software Architecture. Angular Devtools Contributor. &lt;a href=&quot;https://egghead.io/q/resources-by-tomasz-ducin&quot;&gt;Egghead Instructor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Still, the trigger for our discussion was his great article (or actually, a rage post): &lt;a href=&quot;https://ducin.dev/what-is-frontend-architecture&quot;&gt;What is Frontend Architecture?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s where we started, but where did we end?&lt;/strong&gt; Our talk went deep into what it actually means to design a frontend architecture – and whether it should have “frontend” prefix at all. We discussed how architecture, whether frontend or backend, isn’t just about structuring files or picking tools. It’s about decisions that shape the system’s core, affect its performance, scalability, and maintainability, and can be tough to change later on.&lt;/p&gt;
&lt;p&gt;Tomasz explained that many frontend developers still think of architecture as file layouts and tool choices when it’s more about the trade-offs, like deciding on a centralised state or choosing when to use micro-frontends. We agreed that while the backend and front end have different demands, the two “camps” could learn a lot from each other. For instance, frontend devs could benefit from backend principles like domain-driven design to better understand the business problem. In contrast, backend devs could pick up on the frontend’s efficient prototyping and structuring of applications into components.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not surprisingly, we also discussed &lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;Conway’s Law&lt;/a&gt;. Who knew?!&lt;/strong&gt; We discussed the impact of various ways to structure our teams, such as cross-functional, front-end and back-end separated, etc.&lt;/p&gt;
&lt;p&gt;One key point was the benefits of creating cross-functional teams to bridge the frontend-backend divide. Teams that work on both frontend and backend deliver features faster and communicate better, lowering the “communication cost.” It’s all about aligning with the business goal and &lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;Conway’s Law&lt;/a&gt;—your system’s structure will reflect the way your teams communicate. So, putting frontend and backend together in the same team creates a smoother, more user-focused workflow.&lt;/p&gt;
&lt;p&gt;Our discussion wrapped up with a big question: do we need separate frontend and backend architectures, or is it all just architecture? What was the answer? Check in the video!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Especially since this video is free for all subscribers!&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;Bon Appétit&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I believe this talk was a nice wrap of the common discussions and issues with frontend vs. backend responsibilities. We covered common challenges and potential solutions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We’re thinking with Tomek about doing a deep dive on the specific topics in Frontend Architecture (e.g. micro frontends, state management, etc.), so please leave us a comment if you enjoyed it and what you would like to cover!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Check also more from Tomek:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://ducin.dev/&quot;&gt;Blog&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.linkedin.com/in/tomasz-ducin-82234a4b&quot;&gt;LinkedIn Profile&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://twitter.com/&quot;&gt;Twitter&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://egghead.io/q/resources-by-tomasz-ducin&quot;&gt;Egghead courses&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://architekturanafroncie.pl/&quot;&gt;His Polish course Architecture on Frontend&lt;/a&gt; (yes, it’s polished!)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check also other webinars!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;webinars&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#webinars&quot; aria-label=&quot;webinars permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Webinars:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing&quot;&gt;#1 - From CRUD to Event Sourcing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short&quot;&gt;#2 - Keep your streams short! Or how to model Event-Sourced systems efficiently&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;#3 - Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice&quot;&gt;#4 - From CRUD to CQRS in practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100&quot;&gt;#5 - Architecture Weekly 100 Edition - Live Q&amp;#x26;A&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;#6 - Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven&quot;&gt;#7 - Design and test Event-Driven projections and read models&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates&quot;&gt;#8 - Slim down your aggregates!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;#9 - Radek Maziarka - Modularization with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;#10 - PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski&quot;&gt;#11 - Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify&quot;&gt;#12 - Jeremy D. Miller: Simplify your architecture with Wolverine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic&quot;&gt;#13 - Yves Goeleven - The Fantastic 9&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-14-mateusz-jendza-why-verified&quot;&gt;#14 - Mateusz Jendza: Why Verified Credentials is the Future of Digital Identity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-15-mario-bittencourt-leveraging&quot;&gt;#15 - Mário Bittencourt: Leveraging BPMN for Seamless Team Collaboration in Software Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-1-sagas-hector-garcia&quot;&gt;#16 - Papers We Love #1 - Sagas (Hector Garcia-Molina, Kenneth Salem)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-16-simple-patterns-for-events&quot;&gt;#17 - Simple patterns for events schema versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-17-andrea-magnorsky-introducing&quot;&gt;#18 - Andrea Magnorsky: Introducing Bytesize Architecture Sessions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-18-laila-bougria-debug-your&quot;&gt;#19 - Laïla Bougriâ: Debug your thinking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;#20 - Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;#21 - Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-22-on-performance-testing/&quot;&gt;#22 - On Performance Testing with Jarosław Pałka&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;#23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;#24 - Frontent Architecture, Backend Architecture or just Architecture? With Tomasz Ducin&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Bootstrapping CRUD with Pongo]]></title><description><![CDATA[The leitmotif of this blog is the event-driven approach. I truly believe that it’s a way to keep our applications closer to business. By…]]></description><link>https://event-driven.io/en/crud_with_pongo/</link><guid isPermaLink="false">https://event-driven.io/en/crud_with_pongo/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/460a24d0a3770557e48cb10508656e1b/b09c1/2024-10-27-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4iAAAuIgGq4t2SAAADJElEQVR42gEZA+b8AHzF4I/FypXGw5fIw53LxJvLypbKm7LYgafOg6nTmavTkKzQe63Qea3Qfq7PfarSeL2+i/Ger+ubpumXoQBzx+SQzuLa15L12Wnx23f33W7lylack13Gw6r28+/79ur26cL54pL/32L/32X/4GD7u1b7lGL7l2D4lV8AZsTofM3409SS+dZY8tVi6tJ5hIV3UFdf4+Pj9/bz19jZ5uToxcXHtrCH9dVj/d1Y+bVM+4tX+IxV9ItXAFjA53LH9M7PiPrTTvrXW4J8WmhvekxQT6KcmNHNyHBwcHh5e8rJxnB7jLujUf/iUPawRfuHUfiHT/WGTwBJt95auOLNyXf70Uv711nRt1+EgoN8eHPPycXU1dDDw8OSkpPNy89+e2znwkj/3Ez4rjz6hEz3hEn0gkkAQ7jhPqnXzcZr/c9B9tBT/9pKzrFMv7GJ4+He1M/L7ezu7uXN38mG6s1l/9dN/NVG96w2+YJM9oRJ8oBGADiy4Dyk08jFYvzOMPbPQvfUZdrAYo6Ld7mwrUtEQrq2uPLp0fPbif7bYv3NO/3SNfasKvJyNe9xMe5tMwAyst9Ij7vBcmDybkDrmG7RvavKrqedmpeRiod8WFGRgXvHtKvazMjVwKv4kG77kXLwdmHrZGPsW2PmY2gArVZx4DFC9zJA9zRBkE5lRoKZR3qPU7DTHYGnGmF/GGSCP3yVY2NpsX2M66rL6afI4J/C5qDE56PD5anEAP8ZKq4IFJUQGe8UIm5NaR2t3SJIXEWNqy287QuIs0ZicSaOtECIr+eGoqwyTYxFVOOTsLpsh1geLbBigADzITOMDBVNDQ/YGCN3RV4ortoLP1UsdZJAxPEplLobRFQnjLBiosjUfph8MkKJUF/qmbizbIZlMUe4dZIA7Cg17iAq6iMs6BgjaTlNNLzqMsL3MrnqM6zXNrnnLb3wM8T2Wpa53Iqj4pi445i31Yqm2IKl5pG12o2sANwxPc8pLrwiJ+QnKmkzQSSu4TuVrzKdvymu3jKw3TOUtiOn0VKZwdiCna1ofLhviNKHqMyEoqNieb12kUGU1uKn0QczAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/460a24d0a3770557e48cb10508656e1b/507e6/2024-10-27-cover.webp 200w,
/static/460a24d0a3770557e48cb10508656e1b/28a80/2024-10-27-cover.webp 400w,
/static/460a24d0a3770557e48cb10508656e1b/0367d/2024-10-27-cover.webp 626w&quot;
              sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/460a24d0a3770557e48cb10508656e1b/36ca5/2024-10-27-cover.png 200w,
/static/460a24d0a3770557e48cb10508656e1b/a3397/2024-10-27-cover.png 400w,
/static/460a24d0a3770557e48cb10508656e1b/b09c1/2024-10-27-cover.png 626w&quot;
            sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/460a24d0a3770557e48cb10508656e1b/b09c1/2024-10-27-cover.png&quot;
            alt=&quot;2024 10 27 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The leitmotif of this blog is the event-driven approach. I truly believe that it’s a way to keep our applications closer to business.&lt;/strong&gt; By doing so, we can better reflect the business process in our system design and code. And that’s great, as it brings multiple benefits: easier evolution, resiliency, and better managed and traced workflows.&lt;/p&gt;
&lt;p&gt;Still, sometimes you don’t need all of that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sometimes you just need a &lt;em&gt;bag for data&lt;/em&gt;, or the &lt;em&gt;CRUD&lt;/em&gt; as most of us prefer to call it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CRUD comes from the common set of operations we perform on our data: Create, Read, Update, and Delete. It’s an implementation style suited for Content Management Systems.&lt;/strong&gt; The responsibility is to store and manage data. Of course, we wrap that with basic validation, consistency rules, and authorisation. We run simple business logic or enrich data with information available only on the backend. You just put some data and retrieve it. What you put is what you get.&lt;/p&gt;
&lt;p&gt;In CRUD, you don’t have specific behaviour. What’s more, from the system side, this data doesn’t have much business context. That’s why implementations are generic. It only has meaning for the user who puts this data inside and retrieves it. The user interprets it upon reading and makes decisions outside of the system based on it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CRUD can also be a valid approach for proof of concepts.&lt;/strong&gt; For instance, if we have a product idea, we bootstrap a basic application without an extensive set of business workflows, just basic data ingestion and visualisation, to ensure that there’s potential in this idea.&lt;/p&gt;
&lt;p&gt;In general, there is nothing to be ashamed of when doing CRUD. It’s a valid implementation style but with limited use cases. If we choose it wisely, then it’s all fine.&lt;/p&gt;
&lt;p&gt;We need also to remember that we should not set this decision in stone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why I like to match CRUD with &lt;a href=&quot;https://event-driven.io/en/cqrs_facts_and_myths_explained/&quot;&gt;CQRS&lt;/a&gt;.&lt;/strong&gt; How come? Aren’t they contradicting? Not in my world!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CQRS stands for Command Query Responsibility Segregation.&lt;/strong&gt; It’s a structural pattern. It tells us to slice our business application by the behaviour and then segregate them into two responsibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Command handling - business logic that can change state but should not be returning business data,&lt;/li&gt;
&lt;li&gt;Query handling - returns data but does not change the state (_“Asking a question should not change the answer”).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If those rules are fulfilled, our internal implementation can be CRUD, and we can apply CQRS principles. That’s essential for proof of concepts. We start with the simple, generic implementation and evolve it once new business requirements appear. As we already segregated our business functionality, we can adjust precisely the places that need to be changed. CRUD doesn’t have to be an unmaintainable amalgamate!&lt;/p&gt;
&lt;p&gt;Still, no matter which way you choose, I believe that Pongo is a decent tool for CRUDs. My way or highway? Nah, I won’t tell you how to live!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why Pongo?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s a Node.js tool, so it’s a lightweight and accessible environment with a big, vibrant community. You should not have issues with potential hiring,&lt;/li&gt;
&lt;li&gt;It runs on PostgreSQL, so it’s easy operational-wise, and it’s easy to set up your hosting in cloud providers, on-premise or services like &lt;a href=&quot;https://neon.tech&quot;&gt;Neon&lt;/a&gt;, &lt;a href=&quot;https://supabase.com/&quot;&gt;Supabase&lt;/a&gt;, &lt;a href=&quot;https://vercel.com&quot;&gt;Vercel&lt;/a&gt;, etc.&lt;/li&gt;
&lt;li&gt;MongoDB-like API is easy to learn and is well-known to many of us.&lt;/li&gt;
&lt;li&gt;The document approach and denormalised data help make it easier to set up your data. It’s well suited for the CRUD model, where you want to store and retrieve data in the same form as you put it,&lt;/li&gt;
&lt;li&gt;Pongo will get you covered with built-in migrations, so there is no need to care a lot about the database schema.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of that makes a good combination for fast bootstrapping.&lt;/p&gt;
&lt;p&gt;For basics, you can check previous articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://event-driven.io/en/introducting_pongo&quot;&gt;Pongo - Mongo but on Postgres and with strong consistency benefits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://event-driven.io/en/pongo_behind_the_scenes/&quot;&gt;Pongo behind the scenes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://event-driven.io/en/pongo_strongly_typed_client/&quot;&gt;Pongo gets strongly-typed client, migrations, and command line tooling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://event-driven.io/en/sql_support_in_pongo/&quot;&gt;Running a regular SQL on Pongo documents&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And &lt;a href=&quot;https://event-driven-io.github.io/Pongo/getting-started.html&quot;&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So pardon me, I won’t repeat myself, especially since you might have read them already. Let me show you some special sauce: Pongo command handling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The typical flow in CRUD for updating records looks as follows:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validate the incoming request.&lt;/li&gt;
&lt;li&gt;Read the current state.&lt;/li&gt;
&lt;li&gt;Do necessary validation based on it.&lt;/li&gt;
&lt;li&gt;Run business logic.&lt;/li&gt;
&lt;li&gt;Update the state.&lt;/li&gt;
&lt;li&gt;Store it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Similarly, for deletion:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validate the incoming request.&lt;/li&gt;
&lt;li&gt;You read the current state.&lt;/li&gt;
&lt;li&gt;Do the necessary validation, checking if you can delete it.&lt;/li&gt;
&lt;li&gt;Delete it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;For creation, it’s even simpler; you just:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validate the incoming request.&lt;/li&gt;
&lt;li&gt;Generate the new state based on request data.&lt;/li&gt;
&lt;li&gt;Store it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;If we’d like to be sneaky, we could wrap it in a single flow that covers all of those cases.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Validate the incoming request.&lt;/li&gt;
&lt;li&gt;Read the current state.&lt;/li&gt;
&lt;li&gt;If a state exists and you want to update it, do the necessary validation based on it.&lt;/li&gt;
&lt;li&gt;Run business logic. Returning new state, updated state, or null if you want to delete the state.&lt;/li&gt;
&lt;li&gt;Depending on the result of the business logic:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Create if state didn’t exist and result state is not null,&lt;/li&gt;
&lt;li&gt;Update if state existed and result state is different and not null,&lt;/li&gt;
&lt;li&gt;Delete if state existed and result state is null,&lt;/li&gt;
&lt;li&gt;Do nothing otherwise, and safely handle idempotency.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And guess what? That’s precisely what Pongo can do for you.&lt;/p&gt;
&lt;p&gt;Let’s use our favourite Shopping Cart example. Types for it could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItem&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PricedProductItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ProductItem &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  unitPrice&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCart&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  productItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Opened&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Confirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  openedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  confirmedAt&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  cancelledAt&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;We could define the basic set of operations:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;adding a product item - that’s possible to not confirmed or non-existing shopping cart,&lt;/li&gt;
&lt;li&gt;removing a product item - that’s possible when we have enough products already in the not confirmed shopping cart,&lt;/li&gt;
&lt;li&gt;confirming non-empty shopping cart (we can confirm it twice, handling idempotence safely),&lt;/li&gt;
&lt;li&gt;cancelling opened shopping cart (we can cancel it twice, handling idempotence safely).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The business logic could look as follows:&lt;/p&gt;
&lt;p&gt;Adding product item:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; addProductItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    now&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Confirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping Cart already closed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; clientId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; now &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCart&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    clientId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    openedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    status&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Opened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    productItems&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentProductItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; shoppingCart&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pi&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
          pi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          pi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unitPrice &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unitPrice&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentProductItem &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    currentProductItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    shoppingCart&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  shoppingCart&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalAmount &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unitPrice &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  shoppingCart&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItemsCount &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; shoppingCart&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Removing product item:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; removeProductItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    now&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Opened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping Cart is not opened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; productItem &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentProductItem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pi&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
          pi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          pi&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unitPrice &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unitPrice&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    currentProductItem &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt;
    currentProductItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Not enough products in shopping carts&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalAmount &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unitPrice &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItemsCount &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Confirming&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; confirm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    now&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping Cart is not opened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Confirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping Cart is empty&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; now &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Confirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;confirmedAt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; now&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cancelling:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cancel &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Confirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Cannot cancel confirmed Shopping Cart&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then we can make a basic Pongo setup:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pongoClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@event-driven-io/pongo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;postgresql://dbuser:secretpassword@database.server.com:3211/mydb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connectionString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongoDb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCarts &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoDb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ShoppingCart&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;shoppingCarts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And plug our code into some request processing pipeline (e.g. web api):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AddProductItemRequest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Request&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token builtin&quot;&gt;unknown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;/clients/:clientId/shopping-carts/current/product-items&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; AddProductItemRequest&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; command &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        unitPrice&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unitPrice&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; shoppingCarts&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;addProductItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For all other endpoints the code will look the accordingly. Isn’t that nice?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I think that’s a simple and quick way to sping up a new CRUD system, or bootstrap a new Proof of Concepts.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Thoughts?&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Don't Oversell Ideas: Trunk-Based Development Edition]]></title><description><![CDATA[Welcome to the new week! We’re searching for simple answers to hard problems. We scroll through social media, getting 10-30 seconds of “life…]]></description><link>https://event-driven.io/en/dont-oversell-ideas-trunk-based-development/</link><guid isPermaLink="false">https://event-driven.io/en/dont-oversell-ideas-trunk-based-development/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We’re searching for simple answers to hard problems. We scroll through social media, getting 10-30 seconds of “life hacks.”.&lt;/strong&gt; Just like in the James Bond movies, the message disappears right after it is read. And that’s a good thing, as it’d be more dangerous if we actually try to apply them in practice.&lt;/p&gt;
&lt;p&gt;I think we’re in times when we’re getting more often edutainment rather than actual education. Our dopamine is boosted by taking a dose of those quick answers. We get the feeling, “hey, I just learned something new!“. But do we?&lt;/p&gt;
&lt;p&gt;The advice-givers usually don’t have bad intentions. Most of the time, they believe that’s great advice, as it worked in their one or two projects. Quite often in none, as it describes their thought experiments.&lt;/p&gt;
&lt;p&gt;The shortness of social media posts or short videos also does not help. To reach a wider audience, it has to be kiss, kiss, bang, bang. And if you believe your message is right, then you should try to reach wide audience, right? When in Rome, do as the Romans do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But that’s a trap. The trap of overselling and changing your message into snake oil: a magical cure for everything.&lt;/strong&gt; It’s even easier to fall into that if you spend too much time thinking about it or have financial incentives.&lt;/p&gt;
&lt;p&gt;Martin Fowler, in his great article &lt;a href=&quot;https://martinfowler.com/articles/authorial-stance.html&quot;&gt;“Advocate, educator, and authorial stance”&lt;/a&gt;, wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I see this as part of the difference between an advocate and an educator. The advocate wants the reader to agree with them, they succeed when the reader uses the technique that’s being advocated. I prefer the role of an educator, I succeed if the reader makes a well-informed decision, even if the reader chooses a different path to the one that I would choose in their situation. (This also means that if the reader does the exact same thing I would have done, but does it just because “Martin said so ”, not understanding the trade-offs properly - then I’ve failed.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s one of those articles I get back quite a lot as a reminder of where I should also be in my work. Am I succeeding? Dunno, you tell me.&lt;/p&gt;
&lt;p&gt;What I know, though, is that there are leitmotifs in my bubble that I see as harmful and misleading. Not because they’re wrong per se but because of how they’re presented. They oversell the idea without explaining to people what they’re signing for. And that’s dangerous.&lt;/p&gt;
&lt;h2 id=&quot;trunk-based-development&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#trunk-based-development&quot; aria-label=&quot;trunk based development permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Trunk-Based Development&lt;/h2&gt;
&lt;p&gt;One of those oversold motifs is &lt;a href=&quot;https://trunkbaseddevelopment.com/&quot;&gt;Trunk-Based Development&lt;/a&gt;. One branch to rule them all.&lt;/p&gt;
&lt;p&gt;What’s Trunk-Based Development? From the &lt;a href=&quot;https://dora.dev/capabilities/trunk-based-development/&quot;&gt;DORA website&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are two main patterns for developer teams to work together using version control. One is to use feature branches, where either a developer or a group of developers create a branch usually from trunk (also known as main or mainline) and then work in isolation on that branch until the feature they are building is complete. When the team considers the feature ready to go, they merge the feature branch back to trunk.&lt;/p&gt;
&lt;p&gt;The second pattern is known as trunk-based development, where each developer divides their own work into small batches and merges that work into trunk at least once (and potentially several times) a day. The key difference between these approaches is scope. Feature branches typically involve multiple developers and take days or even weeks of work. In contrast, branches in trunk-based development typically last no more than a few hours, with many developers merging their individual changes into trunk frequently.&lt;/p&gt;
&lt;p&gt;The following diagram shows a typical trunk-based development timeline:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/3f4737cd1a9ea7bbf121c508299fc640/2024-10-21-cover.svg&quot; alt=&quot;Timelines for versions 1.0 and 1.1, showing a bug fix merged from version 1.0 into the trunk for version 1.1.&quot; title=&quot;Timelines for versions 1.0 and 1.1, showing a bug fix merged from version 1.0 into the trunk for version 1.1.&quot;&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A similar definition can be found in other places.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In a nutshell, Trunk-Based Development is “just” a branching strategy.&lt;/strong&gt; Focused on minimising the lifetime of changes made outside the main branch. That can be done by either committing directly to the main branch or having some short-living branches.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clearly, this definition doesn’t forbid the branches, reviews, pull requests, etc.&lt;/strong&gt; Yet most of the time, trunk-based development is shown as the opposite of them. Too often, we’re being pushed with absolutes like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;pull requests are the root of all evil,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;just do pair reviews,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;no trunk-based development - no continuous integration,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;pair and mob programming and trunk-based development are the only ways to deliver good and fast.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Don’t get me wrong; I like to keep branches short-living, I like to pair, and I’m not a fan of nitpicking in pull request reviews or delivering slowly.&lt;/strong&gt; I’m all for that, but with the right proportions and embracing that different organisations are on different levels of “process maturity.” Not all can just throw away their current practices and turn them upside down, even if the new ones are considered “best practices.”&lt;/p&gt;
&lt;p&gt;Giving advice is not as simple as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;make small and short Pull Requests,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;write simple code,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;use trunk-based development.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Those are thank-you-for-nothing pieces of advice that are not actually actionable.&lt;/strong&gt; All of that represents the outcome of our development process, not the process itself. What does that even mean, short, small, simple? Ask your colleagues; I guess that each person will give a different answer. Applying such simple recipes applied in isolation will not change the overall picture.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I would prefer to see real studies on how to approach organisational evolution, when and how to do it. And when&lt;/strong&gt; &lt;strong&gt;to say “enough is enough”.&lt;/strong&gt; Of course, I don’t want reports like the &lt;a href=&quot;https://www.mckinsey.com/industries/technology-media-and-telecommunications/our-insights/yes-you-can-measure-software-developer-productivity&quot;&gt;(in)fameous McKinsey report on developer productivity&lt;/a&gt;. I don’t want to see reports focusing on the differences in the exact code lines or minutes spent metrics. Most of those I saw are missing the point, not understanding the difference between correlation and causation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Being in the industry for over 17 years is a privilege.&lt;/strong&gt; I remember how it looked when I started. Those were days before StackOverflow existed. They were weird and dark times. I saw the initial trunk-based development and remember why &lt;a href=&quot;https://nvie.com/posts/a-successful-git-branching-model/&quot;&gt;Git Flow&lt;/a&gt; and Pull Requests became popular. There was a reason for that.&lt;/p&gt;
&lt;p&gt;Gilbert Chesterton wrote about this type of change using the metaphor of a fence:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There exists in such a case a certain institution or law; let us say, for the sake of simplicity, a fence or gate erected across a road. The more modern type of reformer goes gaily up to it and says, “I don’t see the use of this; let us clear it away.” To which the more intelligent type of reformer will do well to answer: “If you don’t see the use of it, I certainly won’t let you clear it away. Go away and think. Then, when you can come back and tell me that you do see the use of it, I may allow you to destroy it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, if we want to throw away the fence of code reviews and pull requests, we should understand why they were created and what problem they’re trying to solve.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When I started my career, code repositories weren’t even a standard in the industry.&lt;/strong&gt; Of course, most companies used some code versioning systems, but not all cared enough. The standard was &lt;a href=&quot;https://en.wikipedia.org/wiki/Apache_Subversion&quot;&gt;SVN&lt;/a&gt;. Each company had to keep its own system. SVN had a main branch called trunk, thus “The initial trunk-based development,” as I call it.&lt;/p&gt;
&lt;p&gt;It was a nightmare. SVN didn’t have local branches like Git. So you were actually forced to always push your changes to remote. Of course, you could push it to the remote non-main branch, but the branching merging was a nightmare. Quite often, the team had a person who was called to do the merges, as it required a specific craft not to break the main branch.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Most just didn’t use any branches. In one of my projects, we had a dedicated Skype group to say that we were about to merge a new commit and then send a follow-up to our colleagues asking them to get new changes as fast as possible.&lt;/strong&gt; Without that, you would constantly be hitting merge conflicts. And then, you had to call &lt;em&gt;“the Conflict Resolution Maestro”&lt;/em&gt; to help it. Or if you were keeping this title (as I did), then you were constantly distracted going to help others.&lt;/p&gt;
&lt;p&gt;In addition, unit and automated testing weren’t so common. There was no GitHub or Gitlab, and most companies ignored continuous integration and continuous development practices. That somehow worked sometimes. As the projects were smaller and not distributed, people sat together. And no, it’s not a point for “back to office” practices. The quality was just terrible compared to now.&lt;/p&gt;
&lt;p&gt;No, it wasn’t as pictured now, that if you use trunk-based development, you’ll immediately get better collaboration. Most people didn’t care about that; they were just delivering more code. There were just more arguments about “Who broke my code?!“. People are people.&lt;/p&gt;
&lt;p&gt;Then, our industry expanded, teams became bigger and more distributed, Git came, and better CI/CD tooling, code reviews, and agile practices became popular. They were meant to deal somehow with growing complexity and the terrible quality of the delivery.&lt;/p&gt;
&lt;h2 id=&quot;pull-requests-are-also-not-perfect&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pull-requests-are-also-not-perfect&quot; aria-label=&quot;pull requests are also not perfect permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pull Requests are also not perfect&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Pull Requests and Code reviews obviously have their problems.&lt;/strong&gt; As with everything, they’re just tools. We all saw bikeshedding, arguments about formatting, etc. We were all tired after keeping our branches for too long or having trouble getting feedback from our colleagues.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However, from my experience, these are usually organisational problems that manifest themselves precisely in them.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;lack of trust in colleagues,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;lack of understanding of the process (some programmers believe that issuing a PR is the end of their work),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;not recognizing a review (any review) as something that is part of their job,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;lack of commitment to cooperation with others.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those issues come directly with problems with work organisation and issues in interpersonal cooperation.&lt;/p&gt;
&lt;p&gt;I think that if we don’t trust our collaborators, it’s a big problem in general. Applying, especially literally, rigid rules around Pull Requests can be one of the symptoms. We often do it to have control. But what we usually get instead is a false sense of control.&lt;/p&gt;
&lt;p&gt;Of course, we can say that pair programming or trunk-based development can replace it and that their reasonable implementation will help the process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s true, but if we don’t fix the organisation and communication, just changing the branching model won’t help. Conway’s Law is inevitable.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;[&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 56px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC80lEQVR42o2VO0hbYRTHr4nvJ6VK1fiig7YIobo4ZDAWEgfpIg4+0aWDQ8DFki0FFReLLipOIopgfEDRRR0FBxdRR4cKVXAQI4nGmHu9/55zki9VWmM+OPnuvd/5fuf/Pc6JBsCiUbu8vHz38PDw1TAMbzQaHab+WzIjH9UPB4NBBzP8fr+Vey0UCtlp8BdizUTqzVT+JOa7phrB9uMOETI9HA7r3CezSCSinqOPj48GTyZhn7Wbm5v3BAyqiB6PBwMDA/KytbWFi4uL2IAZE06+0i8uLsLpdOLq6gpxMO7v739o1NfRPoT4w8bGhkmC0d3dLZOampqwvb39DKTregLIvkNDQzLMP7e3tzMaSa+j9d/xh/HxcQH29/fLpMHBQezvx3aDovM+4e5OXLGwsCBAl8uVUEhb9X9gb2/vM1VPm1I4Pz8vwLa2tpeBo6OjZnp6OmpqatDZ2SlgVsvPHR0d8tzX1ye93W5HdnY2WltbXwaOjIyYmZmZKC8vF2c2VsEB6uvrYbFYwONZWVkoLi5GQUEB3G53cmBGRgYqKytRXV2NoqIiTE5O4vT0FIeHh9jd3UVFRYUY++Tn578OZAUMy8nJQVdXF46Pj9HY2Ija2losLy9jenoavC1VVVWpA3mJvNTZ2Vl4vV6MjY3h6OgIDocDe3t7slRW+SqQD+Wpwp6eHlkqHwArXFpawtzcHHhblMJXD4UPgqOrg5mYmMDJyYko29nZQWlpKUpKSmCz2URp0ns4NTUl95CjM1Atq6GhAc3NzQIoKysTGB8K72V7e/s/wA90gSX1rq+vzZaWFlHGILbCwkJYrVakpaXJO0Pz8vKQm5srgQ8ODhKpR6wZjZLbRtUioIoDFQtsbm5iZWUFq6urYmtra1hfX0+8Kzs7O1MZpceBPlW+/PyBwDpVFSMeMSUz4vlJc8ME/ChAUvWGKs5P42/yplpkTS5rNO035fgXgfl8Pgv/DbAFAoFPNMAXy5WiuakKOc/Pz9+qv4A/x/GAJi+lEOYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/61627/image-2.webp 56w&quot;
              sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png 56w&quot;
            sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;Architecture Weekly&lt;/p&gt;
&lt;p&gt;Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/p&gt;
&lt;p&gt;Hey! 😀…&lt;/p&gt;
&lt;p&gt;Listen now&lt;/p&gt;
&lt;p&gt;2 years ago · 6 likes · Oskar Dudycz&lt;/p&gt;
&lt;p&gt;](&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees?utm_source=substack&amp;#x26;utm_campaign=post_embed&amp;#x26;utm_medium=web&quot;&gt;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees?utm_source=substack&amp;#x26;utm_campaign=post_embed&amp;#x26;utm_medium=web&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;In many organisations, people can take the &lt;em&gt;“drop pull requests”&lt;/em&gt; literally as an encouragement to rush and ignore the quality. It can be understood simply as &lt;em&gt;“we’re rushing to the main branch”&lt;/em&gt;. Just like in the old days.&lt;/p&gt;
&lt;p&gt;To make the change permanent, people need to understand why it’s being introduced and how it relates to other organisational changes (hence the reference to Chesterton’s fence). It should be part of the program, not the only change. It’s never just do X.&lt;/p&gt;
&lt;h2 id=&quot;is-it-just&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#is-it-just&quot; aria-label=&quot;is it just permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Is it “just”?&lt;/h2&gt;
&lt;p&gt;I don’t like easy recipes because they only seem simple. Usually, a long tail of other things follows them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For instance, we hear “throw away Pull requests”, and when we try to apply them, we start hearing:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; you actually have to have feature flags so that you don’t push unfinished things to production to clients,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; you need to have a continuous delivery/deployment setup,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; you also have to have the possibility of a quick rollback if something goes wrong,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; you have to have telemetry and metrics to assess whether you haven’t broken something,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; you need to set up the whole environment and test all modules before pushing your code, because once you do, you may break other people’s work,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; it’s not that we don’t do reviews; we just don’t do them in GitHub,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; now we have to do pair programming,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; it would be great if we had mono repo,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; we need to have a proper folder strategy not to have constant conflicts on the rebases,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; we need to use semantic git commits as we’re committing so often and integrating to understand the correlation,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; sometimes we do branches, when? It depends,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;em&gt;and you know,&lt;/em&gt; you need to understand the testing pyramid and where each tests are called,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Of course, all these directions are justified, and we should strive for them; they should be our “North Stars”. We should aim to deliver proper outcomes and processes that help us with that.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But if our process is far from that then it’s not easy, and it requires time, common agreement in the organisation, careful mentoring and onboarding.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I have the impression that many materials do not say anything about this additional package of things that are needed. Some organizations really start from the ground level.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Different work environments require different tools and different forms of cooperation. Some people need more rigid guidelines, others quite the opposite. It depends a lot on the team itself.&lt;/p&gt;
&lt;p&gt;The most important thing is to establish common practices and agree on them. Of course, we need to be flexible and open to change, but only when the team agrees on it. We also need to be careful about picking our battles. In many organisations, any change takes time, and we need to understand what we’re signing for to make a realistic modernisation effort aligned with our state-of-the-art.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I hope you don’t take this article as “don’t do trunk-based development” but rather as an encouragement to go beyond the simple advice and be sceptical of the easy answers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It’s not only about people giving easy answers but also about us who seek them.&lt;/p&gt;
&lt;p&gt;Some of us may consider a simple prescription a “slap on the wrist”, confirming that we don’t have to invest in quality; we just throw out any review.&lt;/p&gt;
&lt;p&gt;Some will mean well, but they won’t be aware of the next steps and will throw the baby out with the bathwater.&lt;/p&gt;
&lt;p&gt;I don’t worry about the first group because they will find a way to do it wrong anyway (although it’s nice to “interfere” with it a bit… ).&lt;/p&gt;
&lt;p&gt;I’m worried about the second one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As architects, leaders, and educators, we should &lt;a href=&quot;https://martinfowler.com/articles/authorial-stance.html&quot;&gt;take a “trade-off stance”, as Martin Fowler nicely phrased it&lt;/a&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In this I assume that this promising technique has advantages compared with the alternatives, but also some weaknesses. My aim as an author is to ensure the reader takes into account all of these factors when deciding whether they want to use the technique themselves. Anything we write has lots of readers (we hope), who each have their own particular context. It’s up to them to evaluate whether a technique that worked well for us will transplant effectively to their context. By priming them with all the factors we know about, we increase the chances of them making a wise decision.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;As advice takers, we cannot take things for granted; we should do our homework and understand what’s hiding behind the advice.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every change carries a cost and trade-offs.&lt;/p&gt;
&lt;p&gt;And sorry, I won’t give you an easy answer in the last sentence. I’m hoping that you’ll do your homework.&lt;/p&gt;
&lt;p&gt;If you have done it already, please reply or leave a comment. I’m curious about your thoughts and experience!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. I’d like to know what’s interesting and what’s not.&lt;/strong&gt; Your thoughts mean much to me, and I’d like to make this newsletter useful for you! I’d be grateful for filling out this short survey: &lt;a href=&quot;https://forms.gle/1k6MS1XBSqRjQu1h8&quot;&gt;https://forms.gle/1k6MS1XBSqRjQu1h8&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s.2.&lt;/strong&gt; &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Running a regular SQL on Pongo documents]]></title><description><![CDATA[Have you heard someone say: “We’ll use this tool because it requires a long onboarding and lots of memorisation?” You could have seen the…]]></description><link>https://event-driven.io/en/sql_support_in_pongo/</link><guid isPermaLink="false">https://event-driven.io/en/sql_support_in_pongo/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/56dfce3259a234d6e422b2689bba799e/b09c1/2024-10-15-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADPklEQVR42j2T228UZRjG5w/Af8Mo8Q5jYrzzwgu88M4TiokkIsYY0yiK8dDqbg+73e2eTzOz59mdndlzd1uyVSECRRASDm2pCiSo2LK2W1oMXP58ZzRevJnMfDO/eZ7nez4lpebRcmVyxSqFco2iYcnVxjCblKt1d03PV+S+5a6rWYOMXiaZKRJP6IQiGWaDcWb8ETxTsyh63nBfdD6u1JruNNp9soUKKS3PtdV1Ll6+yudfeYkmNEy7K2tVtGyVlFomnswRjmYIziWZ9odRHJjzd7PeodaYp95ZIFcyef6FgzQ7XR4++ptfb93i7SPv8PgTT+KdCvwHrYnyKhmtRCKVJxJTCQhUKYs1B2Y157Fa8ywsneKtI0c58MyzlIpFVlauszgYcPjNQ+zb9xgvv3KIbv9bceVEU3eh6f+hGopjsdboYrd7AuzRXVwiEE5w8MWXWF1bo2bZfDkxzvHjY+zf/xTJlI7d7EtEDUqVBvmShZozJNMCMclUqVhtLLFqS252pyeW+y40ksiwfP4iHxx9l+eePsCJT0+IgjSt7iJVqyNZtwXadFVmC6arMpbMolTrAmz+q9CBNboL7rR6J8XqKfRoiC/G3ufV11+j0xtgioCq1ZJpu00oGDZZaUhad4Ci0JBFs9Fx83OgzjTnT9LqD6jaHfK6xvJpyfDwG6h6gaXvz0iGA7cNpUqdnGSpSq0SmRLhuGSYl96VzAaG/LFit7Alx982thiOHnB3uM3GXyPube+yOdxhYzhiuL3HHxsjjFoLPVdx80uoRSLxLIFICiWpFtyHutOtgkG2bApkRyD3OSsZXrm2xtnln7jx823ube26c+f3TcnNcHc4GteZC2v4AwkmfSEU32yMOSGHomkpao4ff/gOK6exsn6T5QuXOHf+EpevrHD1+jpnzl1g/eYdTi8NyAQ9pJMpZnxRJqcjfO0JSBumUYLhtOyoHCEpZsWxEfIRnJxktPuQnb1H3N0Uy1t7rtU/h/dF/QNWV39h7L1jeLw+wvL9jD+GdzrEhMePouYssevslC0npE406ZztIp6Jb4jM+jDzOno8ind8nM8+/ohPxj7EK71MaYZMVaqkMxtylAp0KsQ/Vwb5J+fAp1wAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/56dfce3259a234d6e422b2689bba799e/507e6/2024-10-15-cover.webp 200w,
/static/56dfce3259a234d6e422b2689bba799e/28a80/2024-10-15-cover.webp 400w,
/static/56dfce3259a234d6e422b2689bba799e/0367d/2024-10-15-cover.webp 626w&quot;
              sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/56dfce3259a234d6e422b2689bba799e/36ca5/2024-10-15-cover.png 200w,
/static/56dfce3259a234d6e422b2689bba799e/a3397/2024-10-15-cover.png 400w,
/static/56dfce3259a234d6e422b2689bba799e/b09c1/2024-10-15-cover.png 626w&quot;
            sizes=&quot;(max-width: 626px) 100vw, 626px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/56dfce3259a234d6e422b2689bba799e/b09c1/2024-10-15-cover.png&quot;
            alt=&quot;2024 10 15 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Have you heard someone say: &lt;em&gt;“We’ll use this tool because it requires a long onboarding and lots of memorisation?”&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You could have seen the decision as such in hindsight, but that doesn’t happen too often intentionally. Or at least, I hope.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Tools I built need to share a common goal: they must be accessible and enable advanced users to customise them to their needs.&lt;/strong&gt; Users should be able to start quickly. Best if they could reuse the learnings in other areas. Users should get a learning ladder. That’s why I optimise the API and tooling for the newbies.&lt;/p&gt;
&lt;p&gt;That’s why I wrote &lt;a href=&quot;/en/small_rant_about_software_design/&quot;&gt;Small rant about Software Design&lt;/a&gt;, which is being triggered by yet another tool that thinks too soon about the advanced user. Too often, we forget that we won’t get advanced users if the newbies don’t pass the very first steps of the learning ladder.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why, in &lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt;, I’m trying to join two accessibilities: muscle memory and the Node.js community by reusing the MongoDB client API and PostgreSQL operation easiness and familiarity.&lt;/strong&gt; I think that enables me to ramp up quickly and deliver business value by deploying the first version of your software to production.&lt;/p&gt;
&lt;p&gt;To use &lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt;, you just need to install it:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @event-driven-io/pongo&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Have a PostgreSQL instance working somewhere (e.g. &lt;a href=&quot;https://hub.docker.com/_/postgres&quot;&gt;with Docker&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Connect the client to the instance using database:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;postgresql://postgres:postgres@localhost:5432/postgres&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;cockroachdb

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connectionString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongoDb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Define your document type:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;History&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; street&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Address&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  city&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  street&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  zip&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  history&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; History&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  _id&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  address&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Address&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  tags&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And boom, you can access the collection with all the typing benefits:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoDb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://event-driven.io/en/pongo_strongly_typed_client/&quot;&gt;You can even get the typed client that will make that even smoother&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then you can insert some docs:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; docs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Anita&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     address&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; city&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Wonderland&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; street&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Main St&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Roger&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     address&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; city&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Wonderland&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; street&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Elm St&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
     name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Cruella&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;35&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
     address&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; city&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Dreamland&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; street&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Oak St&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;docs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then you can filter them:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; docs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  address&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; city&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Wonderland&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; street&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Elm St&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Update them:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Anita&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $inc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And all that jazz. MongoDB API has its quirks, but I believe it’s flexible and familiar enough to be easy to use. But…&lt;/p&gt;
&lt;p&gt;That may be my point of view, and other people may disagree. Some people are familiar enough with PostgreSQL JSONB syntax to just use SQL.&lt;/p&gt;
&lt;p&gt;The other reason can be that Pongo doesn’t support some MongoDB syntax yet. I’m continuously working on Pongo to deliver decent compatibility. Still, it may take some time to be fully aligned. I might never reach full compatibility, as the API can have numerous permutations that I didn’t predict. I don’t want to block anyone waiting for the new release with the fix. &lt;strong&gt;I think that SQL is a decent fallback.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That’s why I added a few options for using SQL in the Pongo API.&lt;/p&gt;
&lt;h2 id=&quot;sql-queries&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#sql-queries&quot; aria-label=&quot;sql queries permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;SQL queries&lt;/h2&gt;
&lt;p&gt;If you want to query Pongo with SQL, you can use an SQL helper. It’ll handle the parameter formatting, allowing you to customise it. For instance:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; plainString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/dumbo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wonderland &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;plainString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Wonderland&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; docs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;data @&gt; &apos;{&quot;address&quot;:{&quot;city&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;wonderland&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;}}&apos;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In Pongo documents are stored as regular table, the structure looks as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; users &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    _id           &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;           &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
    &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;          JSONB          &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
    metadata      JSONB          &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _version      &lt;span class=&quot;token keyword&quot;&gt;BIGINT&lt;/span&gt;         &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _partition    &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;           &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;png_global&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _archived     &lt;span class=&quot;token keyword&quot;&gt;BOOLEAN&lt;/span&gt;        &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;FALSE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _created      TIMESTAMPTZ    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _updated      TIMESTAMPTZ    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you see, we’re querying the nested content of the JSON kept in the &lt;em&gt;data&lt;/em&gt; column. Of course, you can access any other column or use data from other tables.&lt;/p&gt;
&lt;p&gt;Your SQL will be placed in the WHERE statement.&lt;/p&gt;
&lt;p&gt;If you’re wondering what’s &lt;strong&gt;@event-driven-io/dumbo&lt;/strong&gt;, then it’s a shared package between &lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt; and &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;sql-updates&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#sql-updates&quot; aria-label=&quot;sql updates permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;SQL updates&lt;/h2&gt;
&lt;p&gt;In the same way, you can also do updates. So instead of using the object-oriented Mongo API, you can do the follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; wonderland &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;plainString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Wonderland&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; oz &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;plainString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Oz&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; docs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateMany&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;data @&gt; &apos;{&quot;address&quot;:{&quot;city&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;wonderland&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;}}&apos;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;jsonb_set(data || &apos;{&quot;address&quot;:{&quot;city&quot;:&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;oz&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;}}&apos;::jsonb, &apos;{age}&apos;, to_jsonb(COALESCE((data-&gt;&gt;&apos;age&apos;)::NUMERIC, 0) + &apos;1&apos;), true)&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first SQL will be placed in the WHERE part, and the next one will be put into the UPDATE SET pipeline.&lt;/p&gt;
&lt;p&gt;This will do the same as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; docs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; address&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; city&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Wonderland&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    $set&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; address&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; city&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Oz&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    $inc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;My point is for the MongoDB syntax. But I’ll let you decide what looks simpler to you.&lt;/p&gt;
&lt;h2 id=&quot;do-whatever-you-want-with-sql&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#do-whatever-you-want-with-sql&quot; aria-label=&quot;do whatever you want with sql permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Do whatever you want with SQL&lt;/h2&gt;
&lt;p&gt;You can also have the freehand mode, where you can draw anything or actually place any SQL. There you have it!&lt;/p&gt;
&lt;p&gt;You can access it from both the Pongo db and the collection. For instance to query only document id and address you can do the following&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoDb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token constant&quot;&gt;SQL&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;SELECT _id, data-&gt;&apos;address&apos; as address from users&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It will return:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;01928fee-d18b-711b-89d7-830ba98585e8&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;address&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Dreamland&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;street&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Oak St&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;01928fee-d18b-711b-89d7-755d49f389f6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;address&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Oz&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;01928fee-d18b-711b-89d7-790f5373cd10&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;address&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;city&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Oz&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;As you can imagine, this means that you can also join with other tables, in general, whatever the SQL syntax allows you.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can also do any other operation that changes the data like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoDb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sql&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;SQL
  UPDATE users 
  SET 
    data = jsonb_set(data || &apos;{&quot;address&quot;:{&quot;city&quot;:&quot;Oz&quot;}}&apos;::jsonb, &apos;{age}&apos;, to_jsonb(COALESCE((data-&gt;&gt;&apos;age&apos;)::NUMERIC, 0) + &apos;1&apos;), true) || jsonb_build_object(&apos;_version&apos;, (_version + 1)::text),
    _version = _version + 1
WHERE 
    data @&gt; &apos;{&quot;address&quot;:{&quot;city&quot;: &quot;Oz&quot;}}&apos;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I think that’s pretty neat and powerful.&lt;/p&gt;
&lt;p&gt;So, if you’re afraid of using Pongo, you’ll get a showstopper in compatibility; hey, I have you covered!&lt;/p&gt;
&lt;p&gt;It’s the same if your favourite language is SQL; you can do whatever you want. Underneath, Pongo uses &lt;a href=&quot;https://node-postgres.com&quot;&gt;node-postgres&lt;/a&gt; with connection pooling enabled by default, so the additional performance issue should not hit you.&lt;/p&gt;
&lt;p&gt;See also the videos where &lt;a href=&quot;/en/pongo_behind_the_scenes/&quot;&gt;I showed all of that live&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thoughts? Yay or Nay?&lt;/p&gt;
&lt;p&gt;If you have more questions, join our &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Discord server&lt;/a&gt; and let’s tackle that together!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Why to measure and make our system observable? How to reason on chaotic world]]></title><description><![CDATA[Welcome to the next week! Did you watch or listen to the last releases with our special guests? On Performance Testing with Jarosław Pałka…]]></description><link>https://event-driven.io/en/why-to-measure-and-make-our-system/</link><guid isPermaLink="false">https://event-driven.io/en/why-to-measure-and-make-our-system/</guid><content:encoded>&lt;p&gt;Welcome to the next week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Did you watch or listen to the last releases with our special guests?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-22-on-performance-testing/&quot;&gt;On Performance Testing with Jarosław Pałka&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maybe you thought that they were sidetracks of the observability series, so the previous editions we did?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-191-is-it-production/&quot;&gt;Is It Production Ready?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/when-logs-and-metrics-arent-enough/&quot;&gt;When Logs and metrics are not enough: Discovering Modern Observability&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/making-your-system-observability/&quot;&gt;Making your system observability predictable&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you did, then that’s not quite the intention I had behind them. Ok, so what was it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I wanted to look on observability, measurements, and instrumentation from different perspectives.&lt;/strong&gt; Observability is not only about the technical CPU metrics or memory usage; it’s about getting insights into our system’s behaviour. Moreover, those insights are not worth a penny if we cannot take benefit of them.&lt;/p&gt;
&lt;p&gt;Before I tell you the story of my project, let’s see what our guests said.&lt;/p&gt;
&lt;p&gt;Gojko highlighted that we live in an unpredictable world and need to accept it. We cannot predict everything, but we can prepare our systems by making them observable, which will at least give us tools for investigation.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some things, you will never be able to explain, like why people, I mean, why people are uploading MP3 files, I don’t know. I’ve tried to get in touch with a few of them. They don’t respond to my emails. Some of these things just happen.&lt;/p&gt;
&lt;p&gt;Some of these things are weird things that are caused by third-party browser extensions or things like that. &lt;strong&gt;There’s going to be a lot of noise. Any kind of exception tracking and error tracking, there will be a lot of noise and then The question is whether you want to dig into that and figure out something.&lt;/strong&gt; It’s just one tool. It’s not the only tool. It’s not the best tool for everything. It’s one tool that I thought would be interesting for people to learn about.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Primarily because I think when something like that happens and when we make a discovery like that, people usually attribute it to luck or serendipity.&lt;/strong&gt; And I think there’s a process to make these kinds of lucky accidents systematic. I think lucky accidents happen to everybody, but you need to understand that a lucky accident happened to you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And you need to understand the context of it to be able to benefit from it.&lt;/strong&gt; Otherwise, you’re missing the opportunity. And I think that’s kind of my lizard optimization is my attempt to make that systematic so that people can approach it in a more systematic way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In my opinion, that also goes pretty well with &lt;strong&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Cynefin_framework&quot;&gt;Cynefin framework&lt;/a&gt;&lt;/strong&gt; .&lt;/p&gt;
&lt;h2 id=&quot;knowns-and-unknowns&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#knowns-and-unknowns&quot; aria-label=&quot;knowns and unknowns permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Knowns and unknowns&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;It states that we have four types of decision-making contexts (or&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;domains&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;): clear, complicated, complex, and chaotic.&lt;/strong&gt; They also add confusion to the mix. And that sounds like a fair categorisation of the problems we face.&lt;/p&gt;
&lt;p&gt;Clear issues we solve on autopilot, chaotic ones we tend to ignore, and complex ones sound like a nice challenge. And complicated problems? We call them tedious.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Complex problems are called&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;unknown unknowns&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt; This is the place where we feel creative; we do an explorer job. We probe sense and respond. The &lt;em&gt;design emerges&lt;/em&gt; and &lt;em&gt;Agile shines&lt;/em&gt;. We solve it, and then we go further into the sunset scenery like a lonesome cowboy. Off we go to the next exciting problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Complicated issues, on the other hand, are&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;known unknowns&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt; They represent something that has to be done. If we have the expertise, we can sense it with our educated gut feeling, analyse it and respond with a solution. In other words, we usually know &lt;em&gt;what&lt;/em&gt; we need to do, but we need to find an exact &lt;em&gt;how&lt;/em&gt; to solve it. Unknowns are more tactical than strategic. (Read also more in my article &lt;a href=&quot;/en/how_to_solve_complicated_problems/&quot;&gt;Not all issues are complex, some are complicated. Here’s how to deal with them&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/07a295fd1f4b4e50ddb9426fce8c8aa7/c60e9/2024-10-14-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 88.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAASABQDASIAAhEBAxEB/8QAGAABAQADAAAAAAAAAAAAAAAAAAECAwX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHtmJtBYFB//8QAGhABAAIDAQAAAAAAAAAAAAAAAQACECExIv/aAAgBAQABBQKyisOWdvmHIY//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAXEAADAQAAAAAAAAAAAAAAAAAAECEg/9oACAEBAAY/AiOZ/8QAGxAAAwACAwAAAAAAAAAAAAAAAAEREDFhgZH/2gAIAQEAAT8hSIOXYtpdkEh6ORrWNMf/2gAMAwEAAgADAAAAENfPPP/EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8QH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8QH//EABsQAQACAwEBAAAAAAAAAAAAAAEAERAhMUFR/9oACAEBAAE/EAAlddMH9UhsiS6G9RiBR7tJ8jatWKiiX4t4FHH/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/07a295fd1f4b4e50ddb9426fce8c8aa7/507e6/2024-10-14-cover.webp 200w,
/static/07a295fd1f4b4e50ddb9426fce8c8aa7/28a80/2024-10-14-cover.webp 400w,
/static/07a295fd1f4b4e50ddb9426fce8c8aa7/8d2ea/2024-10-14-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/07a295fd1f4b4e50ddb9426fce8c8aa7/37402/2024-10-14-cover.jpg 200w,
/static/07a295fd1f4b4e50ddb9426fce8c8aa7/4cda9/2024-10-14-cover.jpg 400w,
/static/07a295fd1f4b4e50ddb9426fce8c8aa7/c60e9/2024-10-14-cover.jpg 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/07a295fd1f4b4e50ddb9426fce8c8aa7/c60e9/2024-10-14-cover.jpg&quot;
            alt=&quot;Cynefin. Source: https://en.wikipedia.org/wiki/Cynefin_framework#/media/File:Cynefin_framework_2022.jpg&quot;
            title=&quot;Cynefin. Source: https://en.wikipedia.org/wiki/Cynefin_framework#/media/File:Cynefin_framework_2022.jpg&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;While doing design sessions, building our user personas, and interviewing domain experts and potential users, we’re trying to discover as much as we can and make some predictions on the outcomes. By that, we’re trying to reduce our Complex and Chaotic problems into smaller, manageable, Clear or Complicated features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, as &lt;a href=&quot;https://gojko.net/2024/09/30/from-bugs-to-beam/&quot;&gt;Gojko nicely explained in his recent article&lt;/a&gt;, there’s always a potential mismatch between our and users’ expectations:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b7f7f1acf9500fc8fbd55026ce02a4d3/2fc2e/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 73.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAACXElEQVR42o1Ui07bMBTt//8RSGxiAg3GOm1UPFagaROSpnknjhM7jn127UA3HpNwdHTt69uT+zjNrOAlyq5ExnOHnFA4m6HoJl/WZqi6Yrqj2EZU4LIGe0LT19ixHAnLMPNSifmaYZ0JbAi//BZJM+Am6rB45HuflwpIZYhIwyffZcBxv+tdzE+6v4m4S2LGhUbZSnRypDcpFLRvhSKMqDsC+So+IG8HIpv8aSPh572zcS0oAYlejXisI8xAy9DzvJ73pSyxyhNojQ8tbTTWRYCZurlFf3kJFccYkwTy6gpyeQe1ecBt0oJTRvoxwMgYdNfBSOlidd9jCAKoKIJ9qzYGXr7BTIcRxPU1xGIBaa3Fcgn4G6xzgbZs0H/+hO7kxIEfHIAfH6O/uAA/OgI/PIROM1fXKlu/LflvCQY/Ng3kMFIARRCs1U0DM5JvnPwUaAlcySuboSOkg7scBrJT0wQRffldQo3m3Z7ZJJRR+2ReEU7OLorBWA8+aJr2gNO7imTBsAgaJ6WmJyV0CsyqgQvc5yHkKPeE3mtCxiW2JIO400j4iKuwxUPWkxwUglKSHRDQ3q8k6ZNkw4a3U96XbJ00MXVxTk1OAM6Q9xqyqoHdFlASOktgLKriXdn4ZfiSUIUh+Mkp+NcziLMzlOTmQQgx/+783fk3dPM5BEnLYGr7v0MM63giJBW55lpiW76xapYCKfXRDsfFCOn85qlFL0CPzTBukndkY55OLcPO20Kaj/1TLHHa5pgVvHJfiYjSDagH68J30/JSD8vtPYl1M53/AzsI+1vLYb82fwA7Q3/NtdP2vwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/b7f7f1acf9500fc8fbd55026ce02a4d3/507e6/image-2.webp 200w,
/static/b7f7f1acf9500fc8fbd55026ce02a4d3/28a80/image-2.webp 400w,
/static/b7f7f1acf9500fc8fbd55026ce02a4d3/8d2ea/image-2.webp 800w,
/static/b7f7f1acf9500fc8fbd55026ce02a4d3/68fc1/image-2.webp 1200w,
/static/b7f7f1acf9500fc8fbd55026ce02a4d3/43d96/image-2.webp 1600w,
/static/b7f7f1acf9500fc8fbd55026ce02a4d3/1e4d0/image-2.webp 2108w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/b7f7f1acf9500fc8fbd55026ce02a4d3/36ca5/image-2.png 200w,
/static/b7f7f1acf9500fc8fbd55026ce02a4d3/a3397/image-2.png 400w,
/static/b7f7f1acf9500fc8fbd55026ce02a4d3/a331c/image-2.png 800w,
/static/b7f7f1acf9500fc8fbd55026ce02a4d3/8537d/image-2.png 1200w,
/static/b7f7f1acf9500fc8fbd55026ce02a4d3/1a152/image-2.png 1600w,
/static/b7f7f1acf9500fc8fbd55026ce02a4d3/2fc2e/image-2.png 2108w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/b7f7f1acf9500fc8fbd55026ce02a4d3/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gojko.net/2024/09/30/from-bugs-to-beam/&quot;&gt;https://gojko.net/2024/09/30/from-bugs-to-beam/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The best case is when we have alignment with our users and reach an acceptable outcome. The others are least preferable; we’re getting&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;a &lt;strong&gt;bug&lt;/strong&gt; if, together with users, we observe the unexpected behaviour. That’s still easy, as we both agree that this is unexpected.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;an &lt;strong&gt;exploit&lt;/strong&gt; if users’ expectations are far from ours. This can be a security breach or abusing our system in an unpredictable way. This can either make our pricing strategy ineffective or even kill our business. The most famous is the &lt;a href=&quot;https://en.wikipedia.org/wiki/Knight_Capital_Group&quot;&gt;Knight Capital One case, where one mistake in deployment caused a $440 million loss in 45 minutes&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a &lt;strong&gt;mismatch&lt;/strong&gt; if what we expected is unexpected by users. That means that what we deliver is either not aligned with user needs or solving their needs incorrectly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;We need data to understand whether our expectations about complexity and user needs match reality.&lt;/strong&gt; And here’s my story.&lt;/p&gt;
&lt;h2 id=&quot;a-clickbait-feature-goes-rogue&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#a-clickbait-feature-goes-rogue&quot; aria-label=&quot;a clickbait feature goes rogue permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;A clickbait feature goes rogue&lt;/h2&gt;
&lt;p&gt;We were making a cloud version of our legacy product. By legacy, I mean that it was written in an old tech stack but still used and paid for by the users. It was the system used to manage, share and exchange big project files (one file could have over a few gigabytes).&lt;/p&gt;
&lt;p&gt;The project solved the needs of a specific niche business domain. New customers were asking for a cloud solution. They didn’t want to install software on-premise. Also, some current clients wanted easier remote access to their on-premise systems (e.g., through mobile, web interface, etc.). Still, the majority was fine with the current system, and persuading them to move to the cloud was hard.&lt;/p&gt;
&lt;p&gt;For the company, the intention was to, of course, follow the Pareto rule and deliver 20% of features that give 80% of benefits for the current users, getting lower maintenance costs and more flexibility to move forward.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The product and sales team decided to bait the current customers by exposing full-text capabilities for the on-premise files through the cloud.&lt;/strong&gt; Teams added a small change that briefly pushed parsed files (meta)data from on-premise to the cloud. Then, it was indexed in Elasticsearch and exposed in Web and Mobile through the API.&lt;/p&gt;
&lt;p&gt;Then boom bada bing, bada boom. Big announcement to all on-premise customers that they can enable that for free and use it through new Cloud services. Of course, most of the customers enabled it. For free is a decent price, right?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, great success? Not!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/1eb2d8990ec08aca7c7edfab9c460fde/image-3.gif&quot; alt=&quot;Borat Not GIFs | Tenor&quot; title=&quot;Borat Not GIFs | Tenor&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;At some point, management wasn’t happy with the costs of our cloud solution.&lt;/strong&gt; Although only a few companies were using it, the spending was high, so an investigation started.&lt;/p&gt;
&lt;p&gt;We gathered some metrics, and it appeared that most spending is on Elasticsearch. Yet, we didn’t know why, as there was no proper instrumentation. Elasticsearch was used in a few places in our system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After adding proper observability, it appeared that our full-text indexing of on-premise files caused around 90% (or more) of spending.&lt;/strong&gt; Then we compared it with the business usage of the Cloud search feature, and you know where is it going.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Only one customer was using it, and rarely.&lt;/strong&gt; Before I close the curtain, I’ll add that when we asked them if they were fine with removing this feature, they immediately waved their hands and said: yeah, kill it; we don’t need it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;That was a lesson that mismatches can be extremely harmful; the same goes for exploits.&lt;/p&gt;
&lt;p&gt;That’s why I’m recommending now to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;define the expected &lt;strong&gt;measurable&lt;/strong&gt; outcome of our feature, ensuring that we produce enough data to measure it. This can be regular business data, telemetry, or events in the analytics system. Whatever works for us.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;build the metrics and verify the proceeding in some time,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;review and recap how it is going, whether we need to pivot or even kill a feature.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Still, by that, we’re covering known knowns and unknown unknowns. We need to produce data and gather error reports to analyse those unknowns. That’s why I like the idea of &lt;a href=&quot;https://leanpub.com/lizardoptimization/c/i12lFE3oPrg9.&quot;&gt;Lizard Optimization&lt;/a&gt;. It allows us to provide systematic experiments to gain more insights into the complex, chaotic world.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mechanical-sympathy-and-observability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#mechanical-sympathy-and-observability&quot; aria-label=&quot;mechanical sympathy and observability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Mechanical sympathy and Observability&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Looking from a purely technical perspective, our classical measurements, metrics, alarms, etc., represent either known knowns or known unknowns—things&lt;/strong&gt; that we can predict.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Known knowns?&lt;/strong&gt; For instance, we can predict that our disk space may become filled up. We can set up alarms to inform us when our disk space is reaching the limit. We can schedule the archiving process and clean up space periodically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Known unknowns?&lt;/strong&gt; We know that, technically, our CPU may reach 100% or that we have some memory leak that will cause our RAM to fill up. Of course, if we know that our code may cause memory leaks or overflows, we should fix that issue, right? Right? &lt;a href=&quot;https://www.i-programmer.info/news/149-security/8548-reboot-your-dreamliner-every-248-days-to-avoid-integer-overflow.html&quot;&gt;Right Boeing&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;Still, failures will happen. We’re not perfect, and we need to understand that we know the possible outcome. But we may not know the reason in advance.&lt;/p&gt;
&lt;p&gt;And the most dangerous parts are also related to unknown unknowns, especially in the complex domain or technical solutions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We can also map those two quadrants to technical solutions.&lt;/strong&gt; Cynefin, I think that’s obvious. The expectations? Think about our expectations and the reality of how our systems behave. I’m sure you had cases where the technology, framework, and deployment environment behaved much differently than we expected.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In that case, we’re the users and cloud providers, and the tools creators are the authors. We’re just switching places, but the consequences are similar.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;costs-of-observability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#costs-of-observability&quot; aria-label=&quot;costs of observability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Costs of observability&lt;/h2&gt;
&lt;p&gt;Yet, we need to be real. Everything has its costs. Not all companies are in such an excellent situation to &lt;a href=&quot;https://blog.pragmaticengineer.com/datadog-65m-year-customer-mystery/&quot;&gt;lightheartedly pay $65M/year for observability&lt;/a&gt;. None of those I was working on were, and I bet that yours also is.&lt;/p&gt;
&lt;p&gt;That’s why we must think about the strategy and try different ways to build our observability strategy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jarek Pałka gave such a recommendation on strategy for balancing the load test costs:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I would start with a high-level end-to-end test. And then use regressions.&lt;/p&gt;
&lt;p&gt;For example, if you find regression and identify that this code path caused it, then say, “Okay, I will write a specific benchmark for this specific because we know this code already regressed.”&lt;/p&gt;
&lt;p&gt;You can put some criteria, for example, like, okay, if it regresses three times over like, you know, six months, it means, okay, there’s a lot of changes probably going in this code, people are constantly changing, or the requirements are changing, whatever reason for changes.&lt;/p&gt;
&lt;p&gt;So then you use this high-level test to say, okay, which things need to be covered with benchmarks, like more specific. So I would go from, like, you know, top-level end-to-end because they will give you a better safety net.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That makes sense because you already get hints on what to focus on without spending too much time. And we all know that performance is not a priority unless it starts to be a priority.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Taking that back to a higher level makes our system observable. We can start with known-knowns and known unknowns.&lt;/strong&gt; Metrics are cheap, as they’re snapshots of the last known system state. If we focus on the business metrics and analyse our product in terms of complexity and user needs, we can use them to reflect known known and known unknowns.&lt;/p&gt;
&lt;p&gt;Yet, we should not stop on that. We must provide granular and rich observability data so that traces and events capture technical and product contexts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How can we make detective work if our system leaves no traces?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We need to correlate data, dice and roll it, as I showed in &lt;a href=&quot;/en/when-logs-and-metrics-arent-enough/&quot;&gt;When Logs and metrics are not enough: Discovering Modern Observability&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To limit costs, we should define our observability pipeline, which includes filtering, retention policy, sampling and other tools that can make our data meaningful enough but also cost-effective.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But that I’ll try to cover in the next editions.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What are your thoughts and approach to measuring unmeasurable?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And I’d like to know what’s interesting and what’s not.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your thoughts mean much to me, and I’d like to make this newsletter useful for you! Please respond to this email, comment, or fill out this short survey: &lt;a href=&quot;https://forms.gle/1k6MS1XBSqRjQu1h8&quot;&gt;https://forms.gle/1k6MS1XBSqRjQu1h8&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Pongo behind the scenes]]></title><description><![CDATA[If you want to make God laugh, tell him about your plans. My plans were simple: recharge during summer, take a break in July, and then…]]></description><link>https://event-driven.io/en/pongo_behind_the_scenes/</link><guid isPermaLink="false">https://event-driven.io/en/pongo_behind_the_scenes/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 750px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b5615e59034b5ac9b1af736ef0c81005/b13e1/2024-10-11-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADjklEQVR42h2SazPbCRyF/6/2E+x0pm92drqqXbQuizRI1TVBJdGyLg0JCUkICSFXuYhaKmhpdLdKbQw16xa3iI417GB3prtrrOnMfpxn/7Mvztvze37nHOF4PMCRrZ1Ej4E1YwvmvHyyb3xFRUY2TYoqHK1N2JRy8u5kIJPJKMovQJKSjjq/iI5mDYoSOZUlJXQ0NVBbXolwsr7CfshD3KJnSKVEkXqP0pRUuhrreeF1E7S0Y1NXUJwqIe8b0fRONtJbmdSXVOK3deM0GbF3GOjRtfwvIR6d520wgKHgIRX3syhLSUMtecBgp5mwz4vf3IZb84Rh5wBBey/Bvl6G+mz47VZcPZ04uoy4Oo1oqtUUSR4inK+vsvF6hobicmrTc6n5TkJLVTVWTTNOoxG3XotPV8940MNwfw/BHjPeHhODoqHHYsbS2kqfuQN1kZzslGyEy70trn/d4yj2gehwkIjbRdDZT3CgjxGfB69Zj0dbx1jALZJ14+1sx9Ntwtdvwy5+YdIZ6Ld0oiqTU5QrQ/h8tM+VaPh7IsZUyMdCZJL15XnezU4ReRVmyGbB1VzHqEg45OglMGATD9rxO/oYsHRj1bfTrTfQrtPTptEh/HuS4PLjDp9EDYkh+21d7G4tcbC3zM7OEltri7yfecFzMc+gy4HTYsEiUtWVV1OcUcCDpEwU0hKeyFWU55UiXB8luEpsc3USZ9LvJTRgJbG7zNrSLIcHH0gkVtndXaWhXI00KQvJXSmylFyK0mVIvs2nIL2ABqUSVWEZj7LyEP7Y2eTv+Bb/nB4yNznGiMPK+mSIuV4ze5uLnJzGmHv9kvQvb5N2Ixl1iYqQ04Gu7hk5aVKUYplGTSOtdbVoRQlnsTU+iYR/He/zy+JbAq3PWDI1817cZWInytl5HJvWwK0vbiK9nY3P2st69Cci4RFykrOwGtp5NxPG0Pg92qc1CBfbG5ztbXJxuMPxfoxRs5G3VhOHy7Ocnm6xG1ulRlKIIvku/W16se0A0+MjTIf8NBXLGXa5mZ+ZwCeW11StRPi4FCUeXeD8YJvfdjcwqWuZGRvj8s9TPl9f8CY8jiIpmaeZ93kpmqwsvGEi4EV+L4dXz4P8OBUm7B/E2txC/WM1wv7KCpuRaeZsNoxyJaWZhTx+VE2XtoPpH8bRKaqR3PwaiziJyESY2fFRsTwPbpOJn+ciRCMThOx2GqtUtDXU8x+aHGHojNL7AwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/b5615e59034b5ac9b1af736ef0c81005/507e6/2024-10-11-cover.webp 200w,
/static/b5615e59034b5ac9b1af736ef0c81005/28a80/2024-10-11-cover.webp 400w,
/static/b5615e59034b5ac9b1af736ef0c81005/db9b4/2024-10-11-cover.webp 750w&quot;
              sizes=&quot;(max-width: 750px) 100vw, 750px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/b5615e59034b5ac9b1af736ef0c81005/36ca5/2024-10-11-cover.png 200w,
/static/b5615e59034b5ac9b1af736ef0c81005/a3397/2024-10-11-cover.png 400w,
/static/b5615e59034b5ac9b1af736ef0c81005/b13e1/2024-10-11-cover.png 750w&quot;
            sizes=&quot;(max-width: 750px) 100vw, 750px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/b5615e59034b5ac9b1af736ef0c81005/b13e1/2024-10-11-cover.png&quot;
            alt=&quot;2024 10 11 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you want to make God laugh, tell him about your plans.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My plans were simple: recharge during summer, take a break in July, and then return to the regular writing cadence. As you may have noticed, the last part didn’t go entirely as expected.&lt;/p&gt;
&lt;p&gt;Okay, I also had another plan to bootstrap two projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It’s going okay in terms of the ongoing work, okayish in terms of adoption, and not so well yet in making it sustainable, so earning money from it to pay for the effort.&lt;/p&gt;
&lt;p&gt;I also wanted to do less speaking, as it takes time, energy, and travelling, but then I got a few invitations to conferences and webinars that I couldn’t reject.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And that is how we got to Pongo Internals.&lt;/strong&gt; That’s what I showed last week during &lt;a href=&quot;https://www.youtube.com/watch?v=p-6fpV_GDEs&quot;&gt;YugabytDB Community Hours&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/p-6fpV_GDEs?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;And earlier on &lt;a href=&quot;https://www.youtube.com/watch?v=P4r19rv4vOg&quot;&gt;FerretDB Document Database Community&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/P4r19rv4vOg?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;It was tiring, as I was doing between &lt;a href=&quot;https://notesfrompoland.com/2024/09/19/floods-prompt-polands-first-ever-state-of-natural-disaster-what-does-this-mean/&quot;&gt;flood&lt;/a&gt;, &lt;a href=&quot;https://www.axoniq.io/axoniq-conference-2024&quot;&gt;AxonIQ Conference&lt;/a&gt; and having Tonsillitis. But I’ve made it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s heartwarming that tools like FerretDB and YugabyteDB are open to collaboration.&lt;/strong&gt; I’m all for that, so I hope you’ll see more of it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I showcased their recent additions to Pongo like:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fresh new Pongo shell for accessing and manipulating data without setting up the project,&lt;/li&gt;
&lt;li&gt;filtering and updating Pongo documents with custom SQL,&lt;/li&gt;
&lt;li&gt;built-in optimistic concurrency without the typical MongoDB retries,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/pongo_strongly_typed_client/&quot;&gt;typed client&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/pongo_strongly_typed_client/&quot;&gt;migrations&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also shared how it’s working internally, so here’s a bit about the PostgreSQL JSONB magic and other decisions I’ve made.&lt;/p&gt;
&lt;p&gt;Check out if you want to see how sausages are made. And drop your thoughts afterwards!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I think the Pongo concept is unique and can streamline the development of new products.&lt;/strong&gt; If you’re interested in using it or sponsoring some work, &lt;a href=&quot;mailto:oskar@event-driven.io&quot;&gt;contact me&lt;/a&gt;. I’m happy to jump on the call with you, showcase what’s already possible, and discuss how to help your project!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pongo is a community project, and I believe it is a decent place to start your journey in Open Source.&lt;/strong&gt; If you’d like to do it, join our &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Discord server&lt;/a&gt;, and I’ll help you jumpstart your contribution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read more in:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/introducting_pongo/&quot;&gt;Pongo - Mongo but on Postgres and with strong consistency benefits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/pongo_strongly_typed_client/&quot;&gt;Pongo gets strongly-typed client, migrations, and command line tooling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/sql_support_in_pongo&quot;&gt;Running a regular SQL on Pongo documents&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Webinar #23 - Gojko Adzic on designing product development experiments with Lizard Optimization]]></title><description><![CDATA[Welcome to the next week! My favorite conspiracy theory is that the stuff we make in software actually has any sense. That’s a big…]]></description><link>https://event-driven.io/en/webinar-23-gojko-adzic-on-designing/</link><guid isPermaLink="false">https://event-driven.io/en/webinar-23-gojko-adzic-on-designing/</guid><content:encoded>&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Webinar recording&quot; src=&quot;https://www.youtube-nocookie.com/embed/BfI7KVWLKTE?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Welcome to the next week!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;My favorite conspiracy theory is that the stuff we make in software actually has any sense.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;That’s a big conspiracy, and you can see people believing that, and that’s just not true.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I mean, it is in some percentage, but a very, very small percentage.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As you see, we started strong in this week’s episode. That’s a quote from &lt;strong&gt;Gojko Adzic, our very special guest.&lt;/strong&gt; We discussed a special technique created by Gojko: &lt;strong&gt;Lizard Optimization&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Understanding product development for technical people is important to me, so &lt;strong&gt;I’ve decided to open it for everyone so you can watch it without a paywall.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 736px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6c22af1a0aa9311becef6107dbca5078/ca630/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACvklEQVR42lXS7UtTYRgG8H3sc2BhpqbYRFGptJnWKM2FZMuXzbnNOefmdnbO3p3H9r6zNzc3nRMngQmRCBVBkWQfKqJPQf9A/83O1X22XujDzTlw4Hee67ofGZcuiFw6D0bIgUlmYU+kYYsLsEZjsDwJwRIKY9pqQW9/D7ram6Ec7sOUSQ/9WgDzHhd0Xjc93dDQ+yzH1mTubFl0Z0pgU3k4CXUkMrDFBNgicVjDEZg3QlBPqsBNjeAwxuJ9JQ7LnAozDANDIECgpz511O2qyTy5ikgDV2anjkondcQzsEtoNEFwDH47g7JLg28vSvj5+Q2Osjw0zCqMwSAWfL6/qFYC/YV9kQZ1NLvdQCm6hNoiCXioDt3MLNQj/Sh5jNjfcODsaAcMH6jH1vv9hHn/gYHigRgoVuHN74Hig5X6TFKfiSyBSXgyeRjUD/FYOYyTVBDPEx78eHWIUDyCBcIWKfaCz/sndk3my1dFH53QuymBuwRu1WPbqcvVaBKskMXK4jImFYM43Uvj+0kVb59VYOPXqUM/jDT/gZ7cvihhjR7LcKa24KDItkQKK7QYRqCNMyweDPRgz2fBaVXAwZYAE89TXG8Do5n3/Y7Mbe6K7s0Kna6xGEYo1EEmRSOkofPTAgxmLKuUeBpm8LrE48vLYyw6AphjWRj5NWh8tGG6PnNujsBMWXTldmkhUtwSIXk4UjmKE0bf+DTark/g9o2b0N8bQlB7H++qBXw8/YSrA2OQK1SYMCzRT73QEjrDOWsyZ2Zb5LI74KQNp4twUocrEQHyoQmcaxlAc/ct9HZ2IMVacVxMIsEuY/rRPK703kWLfBQXOhXovzOFSbO1AS7xUdG8EYOZj8G0Ll3kGGbtLpy/3IPzXQoox9UIrhiQi0bw9ewDnCYdmi51o717FK30/WLHIJrarqFVPowxrb72C37A7jxsKvNYAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/6c22af1a0aa9311becef6107dbca5078/507e6/image-2.webp 200w,
/static/6c22af1a0aa9311becef6107dbca5078/28a80/image-2.webp 400w,
/static/6c22af1a0aa9311becef6107dbca5078/346f6/image-2.webp 736w&quot;
              sizes=&quot;(max-width: 736px) 100vw, 736px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/6c22af1a0aa9311becef6107dbca5078/36ca5/image-2.png 200w,
/static/6c22af1a0aa9311becef6107dbca5078/a3397/image-2.png 400w,
/static/6c22af1a0aa9311becef6107dbca5078/ca630/image-2.png 736w&quot;
            sizes=&quot;(max-width: 736px) 100vw, 736px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/6c22af1a0aa9311becef6107dbca5078/ca630/image-2.png&quot;
            alt=&quot;https://gojko.net/assets/gojko-adzic-heading.png&quot;
            title=&quot;https://gojko.net/assets/gojko-adzic-heading.png&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I’m a fan of his works, so I’m happy he accepted my invitation. He’s the author of the collaborative technique &lt;a href=&quot;https://www.impactmapping.org/about.html&quot;&gt;Impact Mapping&lt;/a&gt;, and wrote 9 books, including &lt;a href=&quot;https://gojko.net/2020/03/17/sbe-10-years.html&quot;&gt;Specification by Example&lt;/a&gt; and products builder. I like his approach to joining hands-on developers and his mechanical-friendly approach to building IT systems that matter. On my own, I was part of multiple projects that had good architecture and great teams but still failed because the product decisions were wrong and our approach was not aligned.&lt;/p&gt;
&lt;p&gt;Still, moving to the Lizard Optimization.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://gojko.net/books/lizard-optimization/&quot;&gt;Lizard Optimization&lt;/a&gt; is a technique for improving product development by focusing on long-tail users who seem to follow unpredictable, “lizard-like” logic.&lt;/strong&gt; The term comes from the idea that these users often behave unexpectedly, much like a lizard’s erratic movements, which don’t fit typical patterns. Engaging these users can provide fresh insights into your audience and help you refine your product.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 300px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9c4779e3f596bf4c9b3767ede9d2c99c/eed55/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAENElEQVR42o2V6VNTVxiH/bf80jq1LQYJBEVAkkBYIouAC1InEFlEIBsJYFlE6YiKiqKSnc2iLRSVFmeKhk2gncpokUZFViE8PQkKBj+078x7zr13zn1+73LuuTv4xNY3fX1z9n24862L8YNvt0+f7fAPq6urvPW+ZnVhCd6vISiw5vf1LQ8S3hDwfRD+1ALAV3OvKf3xGlUDdoyPbFT3W2l+fBer5wF9U095+nySZ9N/8PzlC2b/nmHR+zYg7ltZxScCWFlZ2QZ89xqVu5pIh4Hkulz2tRQScb2AhCYtcZe0yG6XEec0o7ZXkXqvkazeS2h+bkbbc5nq+7eYnJrcTD0AfDnnDQAjrDquZarQlmdjPKKi8qCM84fjOX4yiYzSDK6myinOUaHTpKAQQrucpcTajAyPjwUDZ0SECQ4LIR0m6tIV9KSr6cxQ06KMpT0zhUGlHGeKkr+SEnGlKHArY7BqswlrN4r3KvGMjgYDZ+fforCbCW03oDQf5ZJOTnNJHCWmNOq1iehOp9OUFY9em8bFJAUPkpI5Z8zlW7E+3m7BMzLyOTDWXoHUqSe820Ler8fI7c1E8/AYlYMqCh5mUtybTm7/UYpaEqhqlCOz65C49SQ6qxkdHdsOfEOMqIUfKHPoCW3TEdKmJ+zWGcLvlLDvTjH7rWdQOgpJ6shHfe8UkW4jYS4dKmcVw2PbUvYuzBFnqyDMqSNcLDzQVk5ycxER4lrqrkDmtKC4okLSqkUiai0R4jKnQQD1yB1mhkaHg4FvFt8FarjXoQtADtwpR1NxRERrJOrGCWKb1Rw6uxN5vYwoWzkRLkMAKP0IHPEEA+cW50mwiqbYy4gUC8M7LcTVHkUq9mKcuxxDdxFNPfWUXElDcjMfaac5UBqpAKtc1TwZ29aU+aVFEq3+VEoDyip3DZHnslEUxmDIz0JfepIaWzGmhlTyK3LZb9MFotznNpHaUYtn+z6cX1xA1WZmj2iAX1ki9uOhy1rOJIeS+UMe6vo8uhQybseGUaPJIFp8Of7SxHRYyOg6x/Cz8QDQt+7bSll5y0hI62lkQl3SbiLnqpbL2RGcajwhOlpBTVYMtQlhSEVdQ0XKUS4TB7uqyeo6z8jE+Oc1lN/Qs+dmMZFt/k6bUN04zdmyFDRVWcQ0HGGvRUFk2S4SasVsLSG6vZL4u9/zXc9FhoY9rCwvs7S0tAFcFqeF+qaZnRdy+Oqalm9ai9gt9t3X9lK+aClg9/njfFmfQ0jdMaIbDxMtuqvqriX9pwsU9V3n0eAg4+MTzP7j3QD6bdo7w8MpD52eAVoe36NhwIWhrxXN3SYy3fWktdcGIFGuSvY7TCg7z5Jxv5G8+1foH3jEu7n5rePrv8z3fpWFhQVevfHy5+wLhqYn+WXiCd1jv2H7vZeZ2VfBNdzo0DprPp84nIPd37n/Yx9/A/8CYcbibrzbpz0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/9c4779e3f596bf4c9b3767ede9d2c99c/507e6/image-3.webp 200w,
/static/9c4779e3f596bf4c9b3767ede9d2c99c/e93cc/image-3.webp 300w&quot;
              sizes=&quot;(max-width: 300px) 100vw, 300px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/9c4779e3f596bf4c9b3767ede9d2c99c/36ca5/image-3.png 200w,
/static/9c4779e3f596bf4c9b3767ede9d2c99c/eed55/image-3.png 300w&quot;
            sizes=&quot;(max-width: 300px) 100vw, 300px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/9c4779e3f596bf4c9b3767ede9d2c99c/eed55/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Gojko developed this method after managing a software product that experienced rapid growth from November 2021 to November 2022, with a critical user metric increasing over 500 times. This turnaround followed a period of stagnation, and it was driven by an unconventional approach to engaging these unpredictable, “lizard-like” users.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;At the end of our discussion, Gojko surprised me by offering a generous 50% book discount link, especially for you, an Architecture Weekly subscriber.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can get it here: &lt;a href=&quot;https://leanpub.com/lizardoptimization/c/i12lFE3oPrg9&quot;&gt;https://leanpub.com/lizardoptimization/c/i12lFE3oPrg9&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Highly recommended, 4.99$ for a good book? Easy choice.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;I’ve read the book and enjoyed it. As with each of Gojko’s works, it’s an easy and funny read but also filled with practical guidance.&lt;/strong&gt; The approach fits more with products and users, so we can track their weird behaviours. Still, you’ll find many additional materials to the complementary techniques that Gojko recommends.&lt;/p&gt;
&lt;p&gt;Some of those that we touched upon during discussions are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.amazon.com/Trustworthy-Online-Controlled-Experiments-Practical/dp/1108724264?crid=2UKN4PA4O39LT&amp;#x26;dib=eyJ2IjoiMSJ9.NPCHXfY3O_Kv4LZrL2m_6flj0ZYe96U_rU_J8kDkn_PGjHj071QN20LucGBJIEps.EfPvA-EIIcsmdrYBNYBJE9aWp4KeCvTpNFwNOw4mRJ4&amp;#x26;dib_tag=se&amp;#x26;keywords=trustworthy+online+controlled+experiments&amp;#x26;qid=1727870546&amp;#x26;sprefix=trustworth+,aps,165&amp;#x26;sr=8-1&amp;#x26;linkCode=sl1&amp;#x26;tag=swingwiki-20&amp;#x26;linkId=079550353e65356116acdd2830f341c8&amp;#x26;language=en_US&amp;#x26;ref_=as_li_ss_tl&quot;&gt;Trustworthy Online Controlled Experiments&lt;/a&gt; by Ron Kohavi&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.amazon.com/Evidence-Guided-Creating-Impact-Products-Uncertainty-ebook/dp/B0CJCDP1H7?crid=2LULQDZ58QM1J&amp;#x26;dib=eyJ2IjoiMSJ9.QJSaev05MQVC3fLue7QrHSJBXof5sCFtFWy9BdJhjNtiglpwaLbCE1ihuzjTBiGGJaQ3pjhQQ-J5ELCvoKXRMsCdbzRW69egu5Hzqd3KT6KMNW_onMUktngIW1Uv44eh1nBW41s0IYRK_1yBzCP47GQ62NpVwh6a1BGZ4RGYWVSJVklUgKKDn0E_oDkXP1chTymwKftwcWfum7jEbsALz4fAX51yCi22VfUQLiz_sEw.ddeNx-XORGFUPhXIZaSxca5BlV-BDOiLj1-YTa_P5n0&amp;#x26;dib_tag=se&amp;#x26;keywords=evidence+guided&amp;#x26;qid=1727870566&amp;#x26;sprefix=evidence+guided,aps,160&amp;#x26;sr=8-1&amp;#x26;linkCode=sl1&amp;#x26;tag=swingwiki-20&amp;#x26;linkId=a0faa9b645956e7edb0babfc71f2f43c&amp;#x26;language=en_US&amp;#x26;ref_=as_li_ss_tl&quot;&gt;Evidence-Guided: Creating High Impact Products in the Face of Uncertainty&lt;/a&gt; by Itamar Gilad&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.votito.com/methods/experimentation-growth-model/&quot;&gt;Experimentation Growth Model&lt;/a&gt; by Aleksander Fabijan&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;One of the technical foundations for being able to be able to experiment with the product is to gather data on the failures or unexpected behaviours. So making it observable. We touched on that in the last editions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/making-your-system-observability/&quot;&gt;When Logs and metrics are not enough: Discovering Modern Observability&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/making-your-system-observability/&quot;&gt;Making your system observability predictable&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And we’ll continue on that!&lt;/p&gt;
&lt;p&gt;Check also the episode with Michael Drogalis on &lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;Building the product on your own terms&lt;/a&gt;. It should be a complementary source for people who are (thinking of) bootstrapping their own products.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are your thoughts and experiences?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Check also other webinars!&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;webinars&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#webinars&quot; aria-label=&quot;webinars permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Webinars:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing&quot;&gt;#1 - From CRUD to Event Sourcing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short&quot;&gt;#2 - Keep your streams short! Or how to model Event-Sourced systems efficiently&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;#3 - Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice&quot;&gt;#4 - From CRUD to CQRS in practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100&quot;&gt;#5 - Architecture Weekly 100 Edition - Live Q&amp;#x26;A&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;#6 - Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven&quot;&gt;#7 - Design and test Event-Driven projections and read models&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates&quot;&gt;#8 - Slim down your aggregates!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;#9 - Radek Maziarka - Modularization with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;#10 - PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski&quot;&gt;#11 - Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify&quot;&gt;#12 - Jeremy D. Miller: Simplify your architecture with Wolverine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic&quot;&gt;#13 - Yves Goeleven - The Fantastic 9&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-14-mateusz-jendza-why-verified&quot;&gt;#14 - Mateusz Jendza: Why Verified Credentials is the Future of Digital Identity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-15-mario-bittencourt-leveraging&quot;&gt;#15 - Mário Bittencourt: Leveraging BPMN for Seamless Team Collaboration in Software Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-1-sagas-hector-garcia&quot;&gt;#16 - Papers We Love #1 - Sagas (Hector Garcia-Molina, Kenneth Salem)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-16-simple-patterns-for-events&quot;&gt;#17 - Simple patterns for events schema versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-17-andrea-magnorsky-introducing&quot;&gt;#18 - Andrea Magnorsky: Introducing Bytesize Architecture Sessions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-18-laila-bougria-debug-your&quot;&gt;#19 - Laïla Bougriâ: Debug your thinking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;#20 - Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;#21 - Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-22-on-performance-testing/&quot;&gt;#22 - On Performance Testing with Jarosław Pałka&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;#23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Webinar #22 - On Performance Testing with Jarosław Pałka]]></title><description><![CDATA[We touched upon performance in the last few editions with: intro to benchmarking in Talk is cheap, show me the numbers! Benchmarking and…]]></description><link>https://event-driven.io/en/webinar-22-on-performance-testing/</link><guid isPermaLink="false">https://event-driven.io/en/webinar-22-on-performance-testing/</guid><content:encoded>&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Webinar recording&quot; src=&quot;https://www.youtube-nocookie.com/embed/eyy-voE6D5I?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;We touched upon performance in the last few editions with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;intro to benchmarking in &lt;a href=&quot;/en/talk-is-cheap-show-me-the-numbers/&quot;&gt;Talk is cheap, show me the numbers! Benchmarking and beyond!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the considerations for &lt;a href=&quot;/en/architecture-weekly-191-is-it-production/&quot;&gt;making our system Production Ready&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Still, I’m not a performance expert here, so I invited &lt;a href=&quot;https://www.linkedin.com/in/jpalka/&quot;&gt;Jarosław Pałka&lt;/a&gt; to our webinar.&lt;/strong&gt; He is an expert.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;He’s the Senior Staff Software Engineer responsible for benchmarking infrastructure in &lt;a href=&quot;https://neo4j.com/&quot;&gt;Neo4j&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/738f31966cb44041e4b3320a3cdf86bf/609ff/image-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAECBAX/xAAVAQEBAAAAAAAAAAAAAAAAAAACAf/aAAwDAQACEAMQAAABRz6G6gV//8QAGRABAQADAQAAAAAAAAAAAAAAAQIAAxET/9oACAEBAAEFAhMdrOO2ljtXfR8s/8QAFhEBAQEAAAAAAAAAAAAAAAAAABFB/9oACAEDAQE/AciP/8QAFhEBAQEAAAAAAAAAAAAAAAAAABJC/9oACAECAQE/AdKf/8QAGxAAAgMAAwAAAAAAAAAAAAAAABEBEjEhUaH/2gAIAQEABj8CnnOy0I3wT0qzT//EABsQAAIDAAMAAAAAAAAAAAAAAAERACExQZGx/9oACAEBAAE/ITiBK0Mc9YzGBOkMLkou4R2TWQrXmf/aAAwDAQACAAMAAAAQmy//xAAXEQEAAwAAAAAAAAAAAAAAAAABECFR/9oACAEDAQE/ECiNj//EABgRAQEAAwAAAAAAAAAAAAAAAAEAESFR/9oACAECAQE/EHaXlhf/xAAcEAEBAQEAAgMAAAAAAAAAAAABESEAMVFhccH/2gAIAQEAAT8QHg8kOguC/PSqAlJ+iuzeeSXYB+c840hHuzhbe+jd63VnP//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/738f31966cb44041e4b3320a3cdf86bf/507e6/image-2.webp 200w,
/static/738f31966cb44041e4b3320a3cdf86bf/28a80/image-2.webp 400w,
/static/738f31966cb44041e4b3320a3cdf86bf/8d2ea/image-2.webp 800w,
/static/738f31966cb44041e4b3320a3cdf86bf/68fc1/image-2.webp 1200w,
/static/738f31966cb44041e4b3320a3cdf86bf/4004c/image-2.webp 1500w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/738f31966cb44041e4b3320a3cdf86bf/37402/image-2.jpg 200w,
/static/738f31966cb44041e4b3320a3cdf86bf/4cda9/image-2.jpg 400w,
/static/738f31966cb44041e4b3320a3cdf86bf/c60e9/image-2.jpg 800w,
/static/738f31966cb44041e4b3320a3cdf86bf/6c738/image-2.jpg 1200w,
/static/738f31966cb44041e4b3320a3cdf86bf/609ff/image-2.jpg 1500w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/738f31966cb44041e4b3320a3cdf86bf/c60e9/image-2.jpg&quot;
            alt=&quot;Jarosław Pałka (@j_palka) / X&quot;
            title=&quot;Jarosław Pałka (@j_palka) / X&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Fun fact: He’s also responsible for me becoming a speaker, as he helped me get first on stage in the SegFault conference he was running. He’s one of the most active members of our Polish IT community, so blame him!&lt;/p&gt;
&lt;p&gt;Getting back to performance tests, benchmarking and that fun stuff.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We discussed how to reason about performance practically, so how not to get mad and build performance-aware systems.&lt;/strong&gt; We focused on the strategies and techniques, not on specific tooling. That you can learn in other places. We went beyond a simple micro-benchmarking showcase.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jarek explained that we have multiple levels of performance tests:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;benchmarking,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;latency tests,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;throughput tests,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;load tests,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;stress tests,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;endurance tests,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;capacity tests,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;degradance tests.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;He also shared his practical advice on starting with them, not getting mad, and not being too afraid even to try that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We also discussed marketing and how to apply lessons from Neo4J performance testing to the regular systems!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;See also more in the materials Jarek recommends:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dendibakh/perf-book&quot;&gt;Denis Bakhvalov - Performance Analysis and Tuning on Modern CPU&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://people.freebsd.org/~lstewart/articles/cpumemory.pdf&quot;&gt;Ulrich Drepper - What Every Programmer Should Know About Memory&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/paulmckrcu/perfbook&quot;&gt;Paul E. McKenney - Is Parallel Programming Hard, And, If So, What Can You Do About It?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.brendangregg.com/systems-performance-2nd-edition-book.html&quot;&gt;Brendan Gregg - Systems Performance&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7IkHIqPeFjY&quot;&gt;Nitsan Wakart — Profilers are lying hobbitses&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;webinars&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#webinars&quot; aria-label=&quot;webinars permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Webinars:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing&quot;&gt;#1 - From CRUD to Event Sourcing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short&quot;&gt;#2 - Keep your streams short! Or how to model Event-Sourced systems efficiently&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;#3 - Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice&quot;&gt;#4 - From CRUD to CQRS in practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100&quot;&gt;#5 - Architecture Weekly 100 Edition - Live Q&amp;#x26;A&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;#6 - Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven&quot;&gt;#7 - Design and test Event-Driven projections and read models&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates&quot;&gt;#8 - Slim down your aggregates!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;#9 - Radek Maziarka - Modularization with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;#10 - PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski&quot;&gt;#11 - Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify&quot;&gt;#12 - Jeremy D. Miller: Simplify your architecture with Wolverine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic&quot;&gt;#13 - Yves Goeleven - The Fantastic 9&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-14-mateusz-jendza-why-verified&quot;&gt;#14 - Mateusz Jendza: Why Verified Credentials is the Future of Digital Identity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-15-mario-bittencourt-leveraging&quot;&gt;#15 - Mário Bittencourt: Leveraging BPMN for Seamless Team Collaboration in Software Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-1-sagas-hector-garcia&quot;&gt;#16 - Papers We Love #1 - Sagas (Hector Garcia-Molina, Kenneth Salem)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-16-simple-patterns-for-events&quot;&gt;#17 - Simple patterns for events schema versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-17-andrea-magnorsky-introducing&quot;&gt;#18 - Andrea Magnorsky: Introducing Bytesize Architecture Sessions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-18-laila-bougria-debug-your&quot;&gt;#19 - Laïla Bougriâ: Debug your thinking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;#20 - Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;#21 - Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;#22 - On Performance Testing with Jarosław Pałka&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-23-gojko-adzic-on-designing/&quot;&gt;#23 - Gojko Adzic on designing product development experiments with Lizard Optimization&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Making your system observability predictable]]></title><description><![CDATA[Welcome to the next week! Last week, we discussed why logs and metrics are not enough. I promised that we’d discuss how to instrument our…]]></description><link>https://event-driven.io/en/making-your-system-observability/</link><guid isPermaLink="false">https://event-driven.io/en/making-your-system-observability/</guid><content:encoded>&lt;p&gt;Welcome to the next week!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/en/when-logs-and-metrics-arent-enough/&quot;&gt;Last week, we discussed why logs and metrics are not enough.&lt;/a&gt; I promised that we’d discuss how to instrument our code. And that’s what we’re going to do today.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I aim to show you how to do it predictably, making observability a quality parameter of our system. It’s not just a bunch of fancy logs.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Speaking about logs, let’s quickly recap what we learnt so far.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Logs give us granular details about what’s happening in the system at a specific time.&lt;/strong&gt; They allow you to read the story of what has happened. Yet, even if they’re structured, they’re fragmented. We often patch them manually to understand how a task moves through the system. Again, this is not the most educated way for troubleshooting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Predefined dashboards showing metrics are great for tracking&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;known unknowns&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;—scenarios we don’t want but know can happen.&lt;/strong&gt; They give us an aggregated view of how your system is performing right now. If the queue size grows or the wait time for acquiring connections increases, it’s a red flag that something’s wrong. However, these metrics are often too high-level to diagnose the root cause.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We know what happened and need to take action, but we don’t understand why.&lt;/strong&gt; We can guess, but guessing in production issues is not the predictable way to handle them. Your boss wouldn’t like to hear that you do Guess Driven Development.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;The alternative is modern Observability tooling based on the Open Telemetry standard. They give us both logical and physical tools.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Logical:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;traces&lt;/strong&gt; - they’re like call stacks for distributed systems. They follow a business operation as it travels through multiple system components, recording every important interaction.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;spans&lt;/strong&gt; - represent single steps in that trace—a unit of work. They contain start and end time, attributes, metadata that adds context to the span (e.g., query type, queue size), and commonly shared values&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;span events&lt;/strong&gt; - milestones or significant events that happen within the span (e.g., query failed, connection acquired).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;metrics -&lt;/strong&gt; as I mentioned, metrics can be helpful in a certain range of scenarios. We can still do them with Open Telemetry.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;instrumenting-observability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#instrumenting-observability&quot; aria-label=&quot;instrumenting observability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Instrumenting Observability&lt;/h2&gt;
&lt;p&gt;To understand what’s happening in our system, we need to instrument it, specify what we need, and describe how we’d like to see it. For instance, we’d like to see how long connection acquisition took or how long the connection was open.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OpenTelemetry gives you automatic instrumentation for standard tools like database connectors, web frameworks, and messaging systems.&lt;/strong&gt; Yet the automatic instrumentation lacks context. Your context. If you want to know why certain queries are slow or which specific task is holding a connection for too long, you need manual instrumentation. This means adding custom spans to your code to record business-specific actions—like when a query on the “orders” table starts, how many rows it returns, and how long it takes to execute.&lt;/p&gt;
&lt;p&gt;Without manual instrumentation, you’re left guessing when things go wrong. It takes more effort, but it’s crucial for getting detailed insights that allow you to diagnose and fix issues quickly. You’re not just tracking the surface-level “what happened,” but also the deeper “why and how,” which is essential in complex systems like connection pooling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, we’re now building the connection pool, so kinda providing the automated instrumentation for others.&lt;/strong&gt; That’s a challenge, as we cannot guess what people may need. That also means we should provide as much information as possible, allowing people to filter out what’s not useful.&lt;/p&gt;
&lt;h2 id=&quot;practical-instrumentation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#practical-instrumentation&quot; aria-label=&quot;practical instrumentation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Practical Instrumentation&lt;/h2&gt;
&lt;p&gt;If we’d like to provide both traces and metrics, we need to define providers accordingly: &lt;em&gt;TraceProvider&lt;/em&gt; and &lt;em&gt;MeterProvider&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;TracerProvider&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;is the central entity keeping the general configuration of how we’d like to handle tracing.&lt;/strong&gt; Like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;sampling strategies (which spans to collect),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;span processors (what to do with spans after they’re created),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;exporters (where to send the span data).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In most setups, you have a single global &lt;em&gt;TracerProvider&lt;/em&gt;. &lt;em&gt;TraceProvider&lt;/em&gt; can create and manage multiple &lt;em&gt;tracers.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Each tracer can be customised for different system parts&lt;/strong&gt;, allowing you to trace specific services or components. You can have different tracers for each module. Each module and team may require different needs, so it’s worth allowing them to diverge. Still, having a central place where you set up basic conventions is a good practice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Accordingly,&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;MeterProvider&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;centralises the metrics handling.&lt;/strong&gt; It lets to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;manage metrics (contextual to the specific feature, component or service),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure metrics export (where to send the span data),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Global access to metrics (allowing your entire application to report metrics consistently).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some metrics can be highly contextual to the business features (e.g., the number of orders processed per minute), while others can be the same for the whole system (e.g., the number of Web API requests handled per second).&lt;/p&gt;
&lt;p&gt;In Node.js and TypeScript, the simplest setup for them could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  MeterProvider&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@opentelemetry/sdk-metrics&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  BasicTracerProvider&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@opentelemetry/sdk-trace-base&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tracerProvider &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BasicTracerProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; meterProvider &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MeterProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    readers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And yes, you need to install the following packages:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; --save @opentelemetry/api @opentelemetry/sdk-metrics @opentelemetry/sdk-trace-base&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cool stuff, but what’s next?&lt;/p&gt;
&lt;h2 id=&quot;predictable-observability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#predictable-observability&quot; aria-label=&quot;predictable observability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Predictable Observability&lt;/h2&gt;
&lt;p&gt;My initial plan for this article was to show you the full Graphana Docker Compose setup, ask you to run it, and show some colourful spans. Something like this from &lt;a href=&quot;https://www.dynatrace.com/news/blog/opentelemetry-demo-identifying-metrics-traces/&quot;&gt;Dynatrace docs&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/bf3f1a7d4610799ff1917f9ed1220846/44866/image-2.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAMABQDASIAAhEBAxEB/8QAFwABAAMAAAAAAAAAAAAAAAAAAAEDBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHaQS0L/8QAGhAAAgMBAQAAAAAAAAAAAAAAAAECERITMf/aAAgBAQABBQJSo3EXmEc4iVL/xAAVEQEBAAAAAAAAAAAAAAAAAAAQIf/aAAgBAwEBPwGn/8QAFREBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQIBAT8BZ//EABkQAAEFAAAAAAAAAAAAAAAAAAEAAiAhMv/aAAgBAQAGPwK3haEf/8QAGhABAQACAwAAAAAAAAAAAAAAAQARMSFBwf/aAAgBAQABPyEBhjcnpMchySu5TqAA0X//2gAMAwEAAgADAAAAEGzP/8QAFhEAAwAAAAAAAAAAAAAAAAAAARAR/9oACAEDAQE/EBS//8QAFhEAAwAAAAAAAAAAAAAAAAAAARAR/9oACAECAQE/EIC//8QAGRAAAwEBAQAAAAAAAAAAAAAAAREhADFh/9oACAEBAAE/ECNqmwLzT8UXoUEThBb1UC9UYRQ7kUIb/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/bf3f1a7d4610799ff1917f9ed1220846/507e6/image-2.webp 200w,
/static/bf3f1a7d4610799ff1917f9ed1220846/28a80/image-2.webp 400w,
/static/bf3f1a7d4610799ff1917f9ed1220846/8d2ea/image-2.webp 800w,
/static/bf3f1a7d4610799ff1917f9ed1220846/68fc1/image-2.webp 1200w,
/static/bf3f1a7d4610799ff1917f9ed1220846/43d96/image-2.webp 1600w,
/static/bf3f1a7d4610799ff1917f9ed1220846/aadcd/image-2.webp 2484w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/bf3f1a7d4610799ff1917f9ed1220846/37402/image-2.jpg 200w,
/static/bf3f1a7d4610799ff1917f9ed1220846/4cda9/image-2.jpg 400w,
/static/bf3f1a7d4610799ff1917f9ed1220846/c60e9/image-2.jpg 800w,
/static/bf3f1a7d4610799ff1917f9ed1220846/6c738/image-2.jpg 1200w,
/static/bf3f1a7d4610799ff1917f9ed1220846/56dca/image-2.jpg 1600w,
/static/bf3f1a7d4610799ff1917f9ed1220846/44866/image-2.jpg 2484w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/bf3f1a7d4610799ff1917f9ed1220846/c60e9/image-2.jpg&quot;
            alt=&quot;OpenTelemetry demo: Identifying metrics and traces&quot;
            title=&quot;OpenTelemetry demo: Identifying metrics and traces&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;That’d be cool. I hear you when you say, &lt;em&gt;“Those articles are nice, but it’d be great if there were more diagrams.”&lt;/em&gt; No worries, we’ll get there. But you can already see that stuff in the vendor blogs. Google it, and you’ll see a lot of results.&lt;/p&gt;
&lt;p&gt;I want to show you today a bit harder, but the sure way—the way inspired by the excellent talk from &lt;a href=&quot;https://martinjt.me/&quot;&gt;Martin Thwaites&lt;/a&gt; (btw. he’s the person you should follow to learn about observability).&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/yVQMnQKSsh4?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;I was sitting in a dark room in Oslo when Martin gave this talk. When I left, I was a bit brainwashed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDW is that Martin showed that we should treat Spans (and other Telemetry data) as our system’s output.&lt;/strong&gt; In other words, we should verify whether we produce the expected telemetry data.&lt;/p&gt;
&lt;p&gt;I wasn’t fully ready for that at the moment I saw this talk, but I already knew that I needed to process it, and I’ll get back to this idea in the future. It took me a while, but the idea constantly circled in my head.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s sad, but many of us haven’t seen a real production system.&lt;/strong&gt; After seven years of my career, I retrospectived and realised that I put only three systems on prod in that span—not a significant number. The startup and venture capital culture of placing high bets is one of the key factors. People are taught to deliver something and sell it as fast as they can or pivot. Many products are not reaching even that phase, running out of money.&lt;/p&gt;
&lt;p&gt;That’s also why we don’t learn how to operate predictable production. We spend most of our time pushing features or discussing whether we should use &lt;em&gt;var&lt;/em&gt;, const, or strongly typed variables. We don’t have the incentive to focus on observability because we won’t need that without going to production.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-191-is-it-production/&quot;&gt;We already discussed how to answer the question, “Is it production ready?”&lt;/a&gt;. And I think that one of the most important features is observability. It can be reflected in database data and events stored or published, but it should also be reflected in Telemetry. That should be a part of our acceptance criteria.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s what Martin was postulating in his talk.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Becoming production-ready is a process. We cannot magically turn the switch and be responsible. We cannot postpone thinking about observability until two weeks before going to production. We cannot just sprinkle it with the observability auto-instrumentation.&lt;/p&gt;
&lt;p&gt;Or actually, we can, but we’ll fail then.&lt;/p&gt;
&lt;p&gt;How can we do it practically, then? Provide the automated setup to ensure that we produce the expected telemetry data.&lt;/p&gt;
&lt;h2 id=&quot;test-driven-instrumentation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#test-driven-instrumentation&quot; aria-label=&quot;test driven instrumentation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Test-Driven Instrumentation&lt;/h2&gt;
&lt;p&gt;Let’s try to instrument our Connection Pool in a Test-Driven Way. We’ll use Node.js and TypeScript, but you can do the same in other environments.&lt;/p&gt;
&lt;p&gt;Let’s start with the basic telemetry setup for our tests:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  AggregationTemporality&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  InMemoryMetricExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  MeterProvider&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@opentelemetry/sdk-metrics&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  BasicTracerProvider&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  InMemorySpanExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  SimpleSpanProcessor&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@opentelemetry/sdk-trace-base&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getTestMetricReader &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./metrics&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getTestOtelSetup&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Trace setup&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tracerProvider &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;BasicTracerProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inMemorySpanExporter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InMemorySpanExporter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  tracerProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addSpanProcessor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleSpanProcessor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inMemorySpanExporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  tracerProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tracer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tracerProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTracer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;test-tracer&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// metrics setup&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inMemoryMetricsExporter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;InMemoryMetricExporter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    AggregationTemporality&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DELTA&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inMemoryMetricsReader &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTestMetricReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inMemoryMetricsExporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; meterProvider &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MeterProvider&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    readers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;inMemoryMetricsReader&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; meter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; meterProvider&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMeter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;test-meter&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    tracer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    meter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    spanExporter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; inMemorySpanExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    metricsExporter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; inMemoryMetricsExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    metricsReader&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; inMemoryMetricsReader&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Btw. it sucks that Substack doesn’t have code highlighting. But I know that I have some blind readers, so images are not an option…&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I’m using the simple in-memory setup here. For now, I don’t want to export it outside (e.g., to Graphana Tempo, Jaeger, Honeycomb, etc.). I just want to have the setup that allows me to check if certain spans and metrics were registered.&lt;/p&gt;
&lt;p&gt;For in-memory metrics, I had to &lt;a href=&quot;https://github.com/open-telemetry/opentelemetry-js/blob/6515ed8098333646a63a74a8c0150cc2daf520db/experimental/packages/opentelemetry-instrumentation-http/test/utils/TestMetricReader.ts&quot;&gt;steal the code from the official repo&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  MetricReader&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PushMetricExporter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@opentelemetry/sdk-metrics&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestMetricReader&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MetricReader&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; _exporter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PushMetricExporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      aggregationTemporalitySelector&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
        _exporter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;selectAggregationTemporality&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bind&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_exporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onForceFlush&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;onShutdown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;collectAndExport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; reject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_exporter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;export&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;resourceMetrics&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;getTestMetricReader&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  inMemoryMetricsExporter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PushMetricExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestMetricReader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;inMemoryMetricsExporter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nothing fancy. The goal of this class is to collect and export metrics data using a provided exporter. Metrics in Open Telemetry are push-based, so we need to have a way to collect and export them. Essentially, the reader just waits for all metrics to be pushed to the exporter. The exporter is responsible for processing and sending metrics data to a backend or another storage.&lt;/p&gt;
&lt;p&gt;Having that, we can set up our tests:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; assert &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;assert&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; beforeEach&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; describe&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; it &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;node:test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; SimpleConnectionPool &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./connectionPool&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getTestOtelSetup &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./otel&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Connection Pool&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pool&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SimpleConnectionPool&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; connectionString&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;    
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; spanExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metricsExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metricsReader &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getTestOtelSetup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;beforeEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;postgres://user:pass@localhost/test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    pool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleConnectionPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      maxConnections&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      maxQueueSize&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      maxWaitingTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    spanExporter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    metricsExporter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// (...) here be dragons. Or tests. Or both.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re setting up telemetry and a connection pool and ensuring that we reset our telemetry data before each test to get proper test isolation.&lt;/p&gt;
&lt;h2 id=&quot;testing-spans&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#testing-spans&quot; aria-label=&quot;testing spans permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Testing Spans&lt;/h2&gt;
&lt;p&gt;Let’s say we’d like to provide information on connection acquisition. This is essential for connection pooling management, as it informs how long it actually took to get a connection. The acquisition may take longer if we reach the maximum number of open connections and need to wait or if we’re opening a new connection instead of just leasing the one already opened.&lt;/p&gt;
&lt;p&gt;The simplest test could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;exports span with acquisition information&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Connection should be successful&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Verify that a span was generated&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; spans &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; spanExporter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFinishedSpans&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;strictEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;spans&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;One span should be generated&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Verify the span name&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; generatedSpan &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; spans&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;strictEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    generatedSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;aw.db.connection_pool.connection_acquiring&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;strictEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    generatedSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    SpanStatusCode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;OK&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course, such a test will fail, as we haven’t added any instrumentation yet. But hey, that’s correct, as we’d like to follow &lt;a href=&quot;https://martinfowler.com/bliki/TestDrivenDevelopment.html&quot;&gt;Red-Green-Refactor&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Let’s add some instrumentation:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleConnectionPool&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConnectionPool&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; isEnded &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; queueBroker&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; QueueBroker&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; tracer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Tracer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConnectionPoolOptions
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queueBroker &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;QueueBroker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      maxQueueSize&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxQueueSize&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      maxActiveTasks&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxConnections&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      maxTaskIdleTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;maxWaitingTime&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PooledConnection&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isEnded&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Connection Pool is ended&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 1. Get current active context&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentContext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 2. Create a new span&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; acquisitionSpan &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tracer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;aw.db.connection_pool.connection_acquisition&apos;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 3. Attach span to active context&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; childContext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; trace&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; acquisitionSpan&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 4. Run code within the context&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;childContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queueBroker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ack &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;openConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ack&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 5. Ending the span&lt;/span&gt;
        acquiringSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; code&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SpanStatusCode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;OK&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        acquiringSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// (..) Dragons and the rest of code&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re injecting here a tracer, which is responsible for recording new spans. Having that, we:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Get current active context.&lt;/strong&gt; The trace needs to start somewhere. Typically, when our web API gets the HTTP request, our code is triggered by the delivery of the message from the queue. That’s handled typically by the auto-instrumentation. Then, we need to get the already initiated context and attach it to it. Without that, it’d be a free rider without referencing a specific context. For our case, the connection pool is usually called as part of application logic, so it’s crucial to attach it to an active request context.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a new span.&lt;/strong&gt; We need to create a specific span that is contextual to our operation. We want to track the acquisition time, so we’re starting that as the first step before even enqueuing it to the queue broker.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Attach span to active context.&lt;/strong&gt; Having the span, we can attach it to the active context, getting the span child context as a result, and…&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Run code within the context.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ending the span with success status.&lt;/strong&gt; After getting the connection (either leased or new), we can close our span successfully. Here, we should ensure with try/catch/finally that we also handle errors and always end span, but following TDD, we should only provide the minimum set of features to satisfy the test, and that’s what we did!&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The final touch is to update the test setup to pass the trace provider:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Connection Pool&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; pool&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SimpleConnectionPool&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; connectionString&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; spanExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metricsExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metricsReader&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tracer &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;getTestOtelSetup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;beforeEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;postgres://user:pass@localhost/test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    pool &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleConnectionPool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tracer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      maxConnections&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      maxQueueSize&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      maxWaitingTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    spanExporter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    metricsExporter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// (...) 🐉🐲&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id=&quot;naming-things&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#naming-things&quot; aria-label=&quot;naming things permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Naming things&lt;/h2&gt;
&lt;p&gt;Few more things here. Naming conventions. We named our span as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;aw.db.connection_pool.connection_acquisition&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can think about span as the stream of events, where attributes are metadata, and events are things that happened during its lifetime. We discussed in &lt;a href=&quot;/en/using-s3-but-not-the-way-you-expected/&quot;&gt;another post how critical it is to select the right key&lt;/a&gt;. The same thing goes with spans. In our case, we’re splitting the span parts with dots, each representing the specific host.&lt;/p&gt;
&lt;p&gt;We start with the prefix representing our library, system, module, etc. In our case, it’s &lt;strong&gt;aw&lt;/strong&gt; (from Architecture Weekly—how smart!). Then we say that it’s related to &lt;strong&gt;db&lt;/strong&gt; operations and &lt;strong&gt;connection_pool&lt;/strong&gt;. And then, we say that our span represents connection acquisition.&lt;/p&gt;
&lt;p&gt;You can read more about span semantic naming conventions (and other OpenTelemetry components) in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://opentelemetry.io/docs/concepts/semantic-conventions/&quot;&gt;OpenTelemetry documentation&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Jeremy Blythe’s article “&lt;a href=&quot;https://www.honeycomb.io/blog/effective-trace-instrumentation-semantic-conventions&quot;&gt;Effective Trace Instrumentation with Semantic Conventions&lt;/a&gt;” on the HoneyComb blog,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Martin Thwaites’ article “&lt;a href=&quot;https://www.honeycomb.io/blog/opentelemetry-best-practices-naming&quot;&gt;OpenTelemetry Best Practices #1: Naming&lt;/a&gt;” is also on the HoneyComb blog.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;testing-metrics&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#testing-metrics&quot; aria-label=&quot;testing metrics permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Testing metrics&lt;/h2&gt;
&lt;p&gt;In OpenTelemetry, we can define the following types of metrics:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Counter -&lt;/strong&gt; a metric that can only increase. It’s typically used to track an event’s total number of occurrences over time, such as the number of requests served or errors encountered. Counters always reset when the system restarts and cannot be decreased.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Gauge&lt;/strong&gt; - a metric that can increase or decrease over time. Gauges are used to track values that fluctuate, like CPU usage, memory usage, or the number of active connections in a pool.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Histogram&lt;/strong&gt; - used to record the distribution of values over a period of time. It’s handy for measuring response times, latencies, or connection wait times. Histograms allow you to capture how often specific values occur within different ranges, helping you understand the distribution of performance metrics.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Let’s define a metric showcasing the connection acquisition time.&lt;/strong&gt; We’ll use the histogram, as each acquisition will have a different value. Based on that, we can calculate measurements like mean, median, etc.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Histogram&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Meter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@opentelemetry/api&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;connectionAcquisitionTimeHistogram&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Meter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
  meter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createHistogram&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;connection_acquisition_time&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Time taken to acquire a connection from the pool&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    unit&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ms&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConnectionPoolMetrics&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  connectionAcquisitionTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Histogram&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// more would come here&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connectionPoolMetrics &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Meter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConnectionPoolMetrics &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  connectionAcquisitionTime&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;connectionAcquisitionTimeHistogram&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;meter&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let’s also add a helper for tests to collect recorded histogram values:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collectHistogramDataPoints &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  metricName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  metricsReader&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; TestMetricReader&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  metricsExporter&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; InMemoryMetricExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; DataPoint&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Histogram&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; metricsReader&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collectAndExport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Check if metrics were recorded&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; metrics &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; metricsExporter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getMetrics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metrics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Metrics should be recorded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// Find the metric&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; acquisitionMetric &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; metrics
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;scopeMetrics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;flatMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sm&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; sm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metrics&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metric&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; metric&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;descriptor&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;metricName&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;acquisitionMetric&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;metricName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; metric should be present&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; acquisitionMetric&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataPoints &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; DataPoint&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Histogram&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And add the new test ensuring that metrics recording was made:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Connection Pool&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;exports acquisition time metric&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pool&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Connection should be successful&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Check the data point for recorded acquisition time&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dataPoints &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;collectHistogramDataPoints&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;aw.db.connection_pool.acquisition_time&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      metricsReader&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      metricsExporter&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;strictEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dataPoints&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;dataPoint&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; dataPoints&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      dataPoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;Connection acquisition time should be recorded once&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ok&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      dataPoint&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;min&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;Connection acquisition time should be recorded and greater than 0&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Yup, simple as that. Method &lt;em&gt;collectHistogramDataPoints&lt;/em&gt; is also an example of how you can wrap this not-so-pleasant API to make testing easier. The test will fail, so let’s instrument our code to record the metric.&lt;/p&gt;
&lt;p&gt;Having it, we can instrument our code to record those times.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleConnectionPool&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConnectionPool&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; tracer&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Tracer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; metrics&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConnectionPoolMetrics&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt; options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConnectionPoolOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// (...)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PooledConnection&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isEnded&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Connection Pool is ended&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; enqueueTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 1. Get current active context&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentContext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;active&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 2. Create a new span&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; acquisitionSpan &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tracer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&apos;aw.db.connection_pool.connection_acquisition&apos;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 3. Attach span to active context&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; childContext &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; trace&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;currentContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; acquisitionSpan&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// 4. Run code within the context&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;childContext&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queueBroker&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ack &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;openConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ack&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; acquisitionTime &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; enqueueTime&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metrics&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;connectionAcquisitionTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;acquisitionTime&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// 5. Ending the span&lt;/span&gt;
        acquiringSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; code&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; SpanStatusCode&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;OK&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        acquiringSpan&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// (..) Dragons and the rest of code&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And bang! We have our code instrumented and verified.&lt;/p&gt;
&lt;h2 id=&quot;to-the-infinity-and-beyond&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#to-the-infinity-and-beyond&quot; aria-label=&quot;to the infinity and beyond permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;To the infinity and beyond!&lt;/h2&gt;
&lt;p&gt;If such tests look to you more like: &lt;em&gt;“Nice, but how can I benefit from it?”&lt;/em&gt; then think further. In the same way, you can orchestrate and test the observability of your business code.&lt;/p&gt;
&lt;p&gt;You can not only ensure that you generate correct spans representing infrastructure context but also the inputs and outputs of your command handler, business logic. You can define business metrics like order processing time and many more.&lt;/p&gt;
&lt;p&gt;Thanks to this approach, you don’t need to play with docker-compose and OpenTelemetry UIs manually and click a lot to see if what you expect is what you get. You can get predictable observability as a part of your core process.&lt;/p&gt;
&lt;p&gt;Of course, you need to be sane in that and test stuff like attributes, events, and recordings. You shouldn’t test all the nitty gritty. Be reasonable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And I’d like to know if I should continue this observability journey. I have more in my buck, as I’m working on instrumenting Emmett and Pongo.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your thoughts mean much to me, and I’d like to make this newsletter be useful for you! Please respond to this email, comment, or fill out this short survey: &lt;a href=&quot;https://forms.gle/1k6MS1XBSqRjQu1h8&quot;&gt;https://forms.gle/1k6MS1XBSqRjQu1h8&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[When Logs and metrics are not enough: Discovering Modern Observability]]></title><description><![CDATA[It’s 2 a.m., and your phone rings. Your system is experiencing performance degradation. Tasks are piling up in the queues, users are seeing…]]></description><link>https://event-driven.io/en/when-logs-and-metrics-arent-enough/</link><guid isPermaLink="false">https://event-driven.io/en/when-logs-and-metrics-arent-enough/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4120a0dc85468468fba92652f68f59a2/af8d6/2024-09-16-cover.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAUBAgME/8QAFQEBAQAAAAAAAAAAAAAAAAAAAQP/2gAMAwEAAhADEAAAAVPZNp0Wmgn/xAAcEAABAwUAAAAAAAAAAAAAAAABAgMhABIiMTP/2gAIAQEAAQUCtpWYO3urkJUJ/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAEDAQE/AYx//8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAECAQE/Aa1//8QAHRAAAAYDAQAAAAAAAAAAAAAAAAIQERIhAUFRgf/aAAgBAQAGPwJrlsRKW2TzAK3E/8QAHBAAAwADAAMAAAAAAAAAAAAAAAERITFBUYGx/9oACAEBAAE/IWuE+AuoiPYvgJpxgzrSpoReOn//2gAMAwEAAgADAAAAEPDv/8QAFxEBAAMAAAAAAAAAAAAAAAAAAAERIf/aAAgBAwEBPxDELP/EABYRAQEBAAAAAAAAAAAAAAAAAAEAEf/aAAgBAgEBPxBakT//xAAcEAEBAAIDAQEAAAAAAAAAAAABEQAhMUFRsWH/2gAIAQEAAT8QHLB0SR03ISol9SfnuLQwmk8cBcmwJzq4ZIQPDvDmaC+5/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/4120a0dc85468468fba92652f68f59a2/507e6/2024-09-16-cover.webp 200w,
/static/4120a0dc85468468fba92652f68f59a2/28a80/2024-09-16-cover.webp 400w,
/static/4120a0dc85468468fba92652f68f59a2/8d2ea/2024-09-16-cover.webp 800w,
/static/4120a0dc85468468fba92652f68f59a2/68fc1/2024-09-16-cover.webp 1200w,
/static/4120a0dc85468468fba92652f68f59a2/43d96/2024-09-16-cover.webp 1600w,
/static/4120a0dc85468468fba92652f68f59a2/f3ff0/2024-09-16-cover.webp 1920w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/4120a0dc85468468fba92652f68f59a2/37402/2024-09-16-cover.jpg 200w,
/static/4120a0dc85468468fba92652f68f59a2/4cda9/2024-09-16-cover.jpg 400w,
/static/4120a0dc85468468fba92652f68f59a2/c60e9/2024-09-16-cover.jpg 800w,
/static/4120a0dc85468468fba92652f68f59a2/6c738/2024-09-16-cover.jpg 1200w,
/static/4120a0dc85468468fba92652f68f59a2/56dca/2024-09-16-cover.jpg 1600w,
/static/4120a0dc85468468fba92652f68f59a2/af8d6/2024-09-16-cover.jpg 1920w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/jpeg&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/4120a0dc85468468fba92652f68f59a2/c60e9/2024-09-16-cover.jpg&quot;
            alt=&quot;Catherine Coulson Dead: &amp;#39;Twin Peaks&amp;#39; Log Lady Dies at 71&quot;
            title=&quot;Catherine Coulson Dead: &amp;#39;Twin Peaks&amp;#39; Log Lady Dies at 71&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s 2 a.m., and your phone rings. Your system is experiencing performance degradation.&lt;/strong&gt; Tasks are piling up in the queues, users are seeing timeouts, and your monitoring dashboard shows that the connection pool is at its limit. Your first instinct? Check the logs. But traditional logging gives you just a fragmented picture: messages without the broader context.&lt;/p&gt;
&lt;p&gt;You’re left wondering: What’s really going on? Why are queues growing? Why are requests taking longer to process? Are there specific queries or requests that are slowing things down? And crucially, how do all these isolated problems connect?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is typically the moment when we realise that our system condition is not the easiest to diagnose.&lt;/strong&gt; It’d be great if we had more data and could understand the context and causation of what has happened.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Understanding not just what went wrong but why and how is one of the foundational features of an observable system—and a system that’s not a nightmare to run in production.&lt;/strong&gt; Nowadays, that’s even more challenging. The complexity of our distributed systems is at its peak. Our problems are rarely isolated to one part of the stack. We need to correlate information from various places to diagnose the issues.&lt;/p&gt;
&lt;p&gt;In the last two releases, we took a break and analysed how S3 could be used for more than storing files and how much it costs. Today, let’s return to the previous series and discuss the typical challenges of distributed systems.&lt;/p&gt;
&lt;p&gt;We’ll continue to use managing a connection pool for database access as an example and discuss the issues and potential troubleshooting using modern observability tools and techniques like Open Telemetry, Grafana Tempo, etc.&lt;/p&gt;
&lt;p&gt;See more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;#189 - Mastering Database Connection Pooling&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;#190 - Queuing, Backpressure, Single Writer and other useful patterns for managing concurrency&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-191-is-it-production/&quot;&gt;#191 - Is It Production Ready?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/talk-is-cheap-show-me-the-numbers/&quot;&gt;#192 - Talk is cheap, show me the numbers! Benchmarking and beyond!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;We’ll explore how traditional monitoring approaches like logs and metrics fail and how observability—through traces, spans, metrics, and span events—gives you the full picture and helps you understand your system deeply.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;connection-pool&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#connection-pool&quot; aria-label=&quot;connection pool permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Connection Pool&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;As discussed in details earlier&lt;/a&gt;, a connection pool manages reusable database connections shared among multiple requests. Instead of opening and closing a connection for each request, we borrow a connection from the pool and return it after database operation.&lt;/p&gt;
&lt;p&gt;Thanks to that, we’re cutting the latency required to establish a connection and get a more resilient and scalable solution.&lt;/p&gt;
&lt;p&gt;That’s okay, but how do you know if it’s working correctly? Let’s start with the traditional approach to monitoring this setup: metrics and logs.&lt;/p&gt;
&lt;h2 id=&quot;traditional-monitoring-with-metrics-and-logs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#traditional-monitoring-with-metrics-and-logs&quot; aria-label=&quot;traditional monitoring with metrics and logs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Traditional Monitoring with Metrics and Logs&lt;/strong&gt;&lt;/h2&gt;
&lt;h3 id=&quot;metrics&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#metrics&quot; aria-label=&quot;metrics permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Metrics&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In a traditional system, metrics provide real-time, aggregated data that helps you track the overall health of your system. For example, in a connection pool, you could monitor the following key metrics:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Active connections:&lt;/strong&gt; This tells you how many database connections are currently in use out of the total available in the pool. It shows you whether the pool is nearing its maximum capacity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Queue size:&lt;/strong&gt; The number of tasks waiting for a connection when all the active connections are in use. If the queue size starts growing rapidly, it’s a sign that your system is under stress and tasks are getting delayed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Connection wait time:&lt;/strong&gt; The average time a task spends waiting in the queue before acquiring a connection. This helps you understand the latency introduced by your connection pool and gives you a signal when tasks are waiting too long.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can imagine the basic metrics dashboard:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Active connections: 95/100
Queue size: 12 tasks
Connection wait time: 200ms&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It could be updated in real-time, brighter and shinier, with colours and animations, etc. Still, the value will be similar to those three lines of text.&lt;/p&gt;
&lt;p&gt;Such a dashboard gives you an aggregated view of how your system is performing right now. If the queue size is growing or the wait time for acquiring connections increases, it’s a red flag that something’s wrong. However, these metrics are often too high-level to diagnose the root cause. You know what happened and that you need to take action, but you don’t understand why.&lt;/p&gt;
&lt;h3 id=&quot;logs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#logs&quot; aria-label=&quot;logs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Logs&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Along with metrics, logs are traditionally used to track events in real-time. You’d log information like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;When a task enters the queue.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When a connection is opened.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When a query is executed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When a connection is released.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example log entries for a connection pool might look like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;INFO: Task enqueued at 12:01:03, queue size: 10
INFO: Connection acquired by Task 101 at 12:01:05
INFO: Query executed for Task 101 at 12:01:10, query time: 500ms
INFO: Connection released by Task 101 at 12:01:12&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Logs give you granular details about what’s happening in the system at a specific point in time. They allow you to trace individual events, but because they’re fragmented, you often must piece them together manually to understand how a task moves through the system. And if you tried to correlate logs on production for a complex system, you’re no longer laughing in the circus.&lt;/p&gt;
&lt;h2 id=&quot;the-pitfalls-of-metrics-and-logs-why-theyre-not-enough&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-pitfalls-of-metrics-and-logs-why-theyre-not-enough&quot; aria-label=&quot;the pitfalls of metrics and logs why theyre not enough permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Pitfalls of Metrics and Logs: Why They’re Not Enough&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;At first glance, metrics and logs seem sufficient for monitoring these systems, but they often don’t give enough context to solve the problem quickly. They show you what’s happening but not why or how. Let’s break down some specific real-world challenges you might face with traditional metrics and logs.&lt;/p&gt;
&lt;h3 id=&quot;issue-1-connection-pool-exhaustion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#issue-1-connection-pool-exhaustion&quot; aria-label=&quot;issue 1 connection pool exhaustion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Issue 1: Connection Pool Exhaustion&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;You’re monitoring a connection pool with a limit of 100 active connections, and suddenly, your metrics dashboard shows that all 100 connections are in use. The queue size is growing, and users are experiencing delays.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Traditional metrics and logs might show you:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;All connections are in use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The queue size is growing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Connection wait times are increasing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;You now know the symptoms, but you still don’t know the root cause:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Are there certain queries that are holding onto connections longer than expected?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Which tasks are being delayed in the queue, and how long have they been waiting?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are there specific requests taking unusually long to complete?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;To diagnose the problem with logs, you would need to stitch together events for each task manually:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Find the log where the task entered the queue.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Find the log where the connection was acquired.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Track the query execution time from separate logs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Cross-reference multiple logs to figure out which requests are slow.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This manual process is slow and inefficient, especially in high-concurrency systems.&lt;/p&gt;
&lt;h3 id=&quot;issue-2-long-query-execution-time&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#issue-2-long-query-execution-time&quot; aria-label=&quot;issue 2 long query execution time permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Issue 2: Long Query Execution Time&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;You notice that queries take longer than expected to execute, contributing to the exhaustion of the connection pool. The logs show when a query was started and completed, but they don’t tell you why it took so long. You might have logs that say:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;INFO: Query executed for Task 102 at 12:02:00, query time: 2000ms
INFO: Connection released by Task 102 at 12:02:03&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;But what’s missing from this?&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Why did the query take so long?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is it related to specific database tables or queries?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Is it a systemic issue or just a one-off slow query?&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The logs provide fragmented data, but they don’t let you see the entire request lifecycle—how long the task spent waiting in the queue, how long it held the connection, and why the query was slow.&lt;/p&gt;
&lt;h3 id=&quot;issue-3-task-starvation-in-queues&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#issue-3-task-starvation-in-queues&quot; aria-label=&quot;issue 3 task starvation in queues permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Issue 3: Task Starvation in Queues&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Imagine your system is queuing incoming requests, and you notice specific tasks get stuck in the queue for long periods. You’re tracking queue size and connection usage, but something isn’t adding up. Some tasks are being processed almost immediately, while others are waiting far too long.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With metrics, you know the queue size is growing, but you can’t see:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Which tasks are stuck in the queue the longest?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Are certain tasks being prioritised incorrectly?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;What’s the actual delay for each individual request?&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Logs only show you when tasks entered the queue and when they were processed. They don’t provide real-time visibility into why tasks are being delayed.&lt;/p&gt;
&lt;h2 id=&quot;moving-beyond-logs-and-metrics-with-observability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#moving-beyond-logs-and-metrics-with-observability&quot; aria-label=&quot;moving beyond logs and metrics with observability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Moving Beyond Logs and Metrics with Observability&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;You need observability to solve these problems and understand your system’s entire lifecycle of requests. Observability goes beyond just gathering data - it’s about understanding how different components interact and why things are happening. With observability tools like traces, spans, metrics, and span events, you can get a complete, real-time picture of what’s happening inside your system.&lt;/p&gt;
&lt;p&gt;Let’s explain how observability tools like traces, spans, and span events give you this deeper insight.&lt;/p&gt;
&lt;h2 id=&quot;what-is-a-trace&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-is-a-trace&quot; aria-label=&quot;what is a trace permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;What Is a Trace?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;A trace is like a call stack for distributed systems. It follows a request as it travels through multiple services and components, recording every important interaction along the way.&lt;/p&gt;
&lt;p&gt;For example, in a connection pooling system, a trace would capture:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;When a request enters the queue.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the request acquires a connection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the request makes a database query.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When the connection is released back to the pool.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Instead of isolated events, a trace links them together to show the complete lifecycle of a request.&lt;/p&gt;
&lt;h2 id=&quot;what-is-a-span&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-is-a-span&quot; aria-label=&quot;what is a span permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;What Is a Span?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;A span represents a single step in that trace—a unit of work. Each span captures:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Start and end times:&lt;/strong&gt; When did the operation start and finish?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Attributes:&lt;/strong&gt; Metadata that adds context to the span (e.g., query type, queue size).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Events:&lt;/strong&gt; Milestones or important events that happen within the span (e.g., query failed, connection acquired).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Think of spans as the building blocks of a trace. They give detailed insights into specific parts of the request lifecycle.&lt;/p&gt;
&lt;p&gt;The basic open telemetry setup can look as follows.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; trace &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@opentelemetry/api&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// setup tracer for connection pool&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tracer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; trace&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getTracer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;connection-pool-tracer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SimpleConnectionPool&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (...) rest of the code&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PooledConnection&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// start new span&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; span &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tracer&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startSpan&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;connect-to-database&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queueBroker
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; ack &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;openConnection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ack&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;// add attributes&lt;/span&gt;
        span&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;pool-size&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;activeTasks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connection&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        span&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// End span when connection is acquired&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; connection&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        span&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setStatus&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; code&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        span&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Don’t worry. We’ll get into the full instrumentation in the next article, but for now, let’s focus on how we can use this data to investigate one of the issues outlined above.&lt;/p&gt;
&lt;p&gt;Let’s walk through a detailed example of how to slice and dice data using OpenTelemetry and &lt;a href=&quot;https://grafana.com/docs/tempo/latest/&quot;&gt;Grafana Tempo&lt;/a&gt; to diagnose a connection pool exhaustion issue. Tempo allows you to query traces and see the filtering results. Why Grafana Tempo? It’s one of the most popular, mature OSS tooling, and I had to choose one for this article’s needs. Other tools like Honeycomb are also capable of doing a similar investigations.&lt;/p&gt;
&lt;p&gt;I’ll guide you step-by-step through drilling into the traces, correlating spans, and finding the root cause. I’ll try to showcase to you the interactive troubleshooting session on how to discover and fix problems using observability tooling.&lt;/p&gt;
&lt;h3 id=&quot;scenario-diagnosing-connection-pool-exhaustion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#scenario-diagnosing-connection-pool-exhaustion&quot; aria-label=&quot;scenario diagnosing connection pool exhaustion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Scenario: Diagnosing Connection Pool Exhaustion&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;You’re noticing intermittent slowdowns in your system, and the connection pool is hitting its limit. Users are seeing delays, and your logs show that the queue size is growing. Traditional logs and metrics show some insights, but you’re unsure which queries or tasks are causing the connection pool to be exhausted.&lt;/p&gt;
&lt;h3 id=&quot;step-1-visualising-the-connection-pool-usage-in-grafana&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#step-1-visualising-the-connection-pool-usage-in-grafana&quot; aria-label=&quot;step 1 visualising the connection pool usage in grafana permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Step 1: Visualising the Connection Pool Usage in Grafana&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;You’ve already instrumented your connection pool and database queries with OpenTelemetry, and all the traces are being sent to Grafana Tempo. Your first task is visualising the traces related to connection pool usage and identifying unusually long connections.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We can start by finding long-lived connections by querying all spans where the&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;connection-acquired&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;span took longer than 1 second to release the connection.&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;span.duration &gt; 1000ms and name = &quot;connection-acquired&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;You may find that several tasks are holding onto connections for longer than 1 second, particularly during peak times.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The longest connection duration was around 5 seconds, which is likely contributing to the pool exhaustion.&lt;/p&gt;
&lt;p&gt;These results suggest that some tasks are holding onto connections longer than they should. However, you still don’t know why these tasks are taking longer to release connections or what is causing the delay.&lt;/p&gt;
&lt;h3 id=&quot;step-2-drilling-down-into-specific-tasks&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#step-2-drilling-down-into-specific-tasks&quot; aria-label=&quot;step 2 drilling down into specific tasks permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Step 2: Drilling Down Into Specific Tasks&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Next, you want to see which specific tasks are holding connections for too long. You’ll need to slice the data based on spans that are part of the &lt;em&gt;connect-task&lt;/em&gt; span (which wraps the connection lifecycle).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We can drill down into specific tasks with long connection durations and correlate these long-lived connections by task ID to understand what operations are performed while the connection is being held.&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;span.duration &gt; 1000ms and name = &quot;connection-acquired&quot; | group_by task-id&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;You see a list of tasks with their&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;task-id&lt;/code&gt; &lt;strong&gt;and corresponding connection durations.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One of the tasks &lt;em&gt;(task-id: 12345)&lt;/em&gt; stands out—it held onto the connection for 5 seconds. Now you know which task is problematic, but you need to dig deeper to understand what’s causing the delay.&lt;/p&gt;
&lt;h3 id=&quot;step-3-correlating-with-query-execution-spans&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#step-3-correlating-with-query-execution-spans&quot; aria-label=&quot;step 3 correlating with query execution spans permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Step 3: Correlating with Query Execution Spans&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;To understand what this task was doing while holding the connection, you’ll need to correlate the task ID with the database query execution spans.&lt;/p&gt;
&lt;p&gt;We need to find the long-running queries for &lt;em&gt;(task-id: 12345)&lt;/em&gt; by querying the &lt;code class=&quot;language-text&quot;&gt;db-query&lt;/code&gt; spans related to this task to see what queries were executed.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;task-id = &quot;12345&quot; and name = &quot;db-query&quot;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You find that task 12345 executed a SELECT query on the orders table:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; orders &lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; order_date &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;2023-01-01&apos;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The span shows that this query took 3.5 seconds to execute, which is far longer than typical queries in your system.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-4-slicing-the-query-data-further&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#step-4-slicing-the-query-data-further&quot; aria-label=&quot;step 4 slicing the query data further permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Step 4: Slicing the Query Data Further&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Next, you want to understand why this query is taking so long. To do this, you can drill down into attributes related to the query execution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can analyse query duration and rows returned&lt;/strong&gt; by looking at the &lt;em&gt;db.rows_returned&lt;/em&gt; attribute to see how many rows this query returned, as returning too many rows could be causing the slowdown.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;task-id = &quot;12345&quot; and name = &quot;db-query&quot; | select db.rows_returned&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;The query returned 50,000 rows, which explains why it took 3.5 seconds.&lt;/strong&gt; This query is pulling back far more data than expected, contributing to the connection being held longer.&lt;/p&gt;
&lt;h3 id=&quot;step-5-correlating-connection-pool-exhaustion-with-query-behavior&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#step-5-correlating-connection-pool-exhaustion-with-query-behavior&quot; aria-label=&quot;step 5 correlating connection pool exhaustion with query behavior permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Step 5: Correlating Connection Pool Exhaustion with Query Behavior&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Now that you’ve identified the problematic query, you want to check if this is a systemic issue or an isolated issue. You can run another query to see if similar queries are being run frequently and whether they’re contributing to connection pool exhaustion.&lt;/p&gt;
&lt;p&gt;We can try to find all long-running SELECT Queries on the orders table&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;name = &quot;db-query&quot; and db.table = &quot;orders&quot; and span.duration &gt; 2000ms&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;You may see that several tasks over the past few days have run similar&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;SELECT&lt;/code&gt; &lt;strong&gt;queries on the&lt;/strong&gt; &lt;code class=&quot;language-text&quot;&gt;orders&lt;/code&gt; &lt;strong&gt;table, each taking between 2-4 seconds.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;These queries are frequently executed during peak hours, leading to multiple tasks holding onto connections longer than expected.&lt;/p&gt;
&lt;h3 id=&quot;step-6-identifying-the-systemic-issue&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#step-6-identifying-the-systemic-issue&quot; aria-label=&quot;step 6 identifying the systemic issue permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Step 6: Identifying the Systemic Issue&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Now that you know the query pattern causing the problem, you can check if these long-running queries correlate with the times when the connection pool is fully utilised.&lt;/p&gt;
&lt;h3 id=&quot;query-6-correlate-long-running-queries-with-high-pool-utilization&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#query-6-correlate-long-running-queries-with-high-pool-utilization&quot; aria-label=&quot;query 6 correlate long running queries with high pool utilization permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Query 6: Correlate Long-Running Queries with High Pool Utilization&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;You can correlate the timing of these long-running queries with connection pool usage by querying for spans where the pool was under pressure.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;span.duration &gt; 2000ms and name = &quot;db-query&quot; and connection.pool.activeConnections &gt; 90&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;You may find a direct correlation: during peak usage, long-running SELECT queries on the orders table are causing the connection pool to hit its limit.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;step-7-solving-the-problem&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#step-7-solving-the-problem&quot; aria-label=&quot;step 7 solving the problem permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Step 7: Solving the Problem&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Now that you’ve identified the root cause of connection pool exhaustion, the next step is to take action.&lt;/p&gt;
&lt;h3 id=&quot;solution-optimise-the-query&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#solution-optimise-the-query&quot; aria-label=&quot;solution optimise the query permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Solution: Optimise the Query&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;You can optimise the query on the &lt;code class=&quot;language-text&quot;&gt;orders&lt;/code&gt; table by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Adding appropriate indexes to speed up retrieval.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Limiting the number of rows returned by using pagination reduces the execution time significantly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;summary&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#summary&quot; aria-label=&quot;summary permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The goal of this article was to showcase why traditional logging and metrics may no longer be sufficient for today’s complex systems. Through the example of diagnosing connection pool exhaustion, we explored how observability 2.0—with traces, spans, and detailed metrics—can provide deeper insights into system behavior.&lt;/p&gt;
&lt;p&gt;Of course, this is just a brief example, but I hope it gives you a sense of what’s possible when you apply observability from the ground up and leverage modern tools like Grafana Tempo. While traditional metrics and logs show you the symptoms, observability lets you correlate data across services, pinpoint bottlenecks, and understand the context behind performance issues.&lt;/p&gt;
&lt;p&gt;We’ve only scratched the surface here. This was a quick walkthrough, but it highlights the power of observability in real-world scenarios. In upcoming articles, we’ll dive deeper into the exact setup, instrumentation techniques, and the trade-offs of adopting observability practices. Stay tuned for more detailed explorations on how to build a highly observable system from the ground up and how to make informed decisions about when to deploy it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And hey, dear reader, I’d like to learn where you’d like to go next. Your thoughts mean much to me, and I’d like to make it engaging for you! Please respond to this email, comment, or fill out this short survey: &lt;a href=&quot;https://forms.gle/1k6MS1XBSqRjQu1h8&quot;&gt;https://forms.gle/1k6MS1XBSqRjQu1h8&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Pongo gets strongly-typed client, migrations, and command line tooling]]></title><description><![CDATA[When you think upfront and want to make things right, there’s an interesting feedback loop. Quite often, things start to click, often in a…]]></description><link>https://event-driven.io/en/pongo_strongly_typed_client/</link><guid isPermaLink="false">https://event-driven.io/en/pongo_strongly_typed_client/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 750px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e864253c01617691383cd1762ddb91db/b13e1/2024-09-13-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4jAAAuIwF4pT92AAADJElEQVR42gEZA+b8ADUjFTMgEioZDBsOBicXC0swIF88KmE9LFw7KmdFM21KOGpHNlo/LUMsHjskFygXDC0bDzAbEDkiFjAcEABCKx5FLB48JRgqGAwqFw1JLSBcOChpRjdrSDlvTj9uUEKLb19vU0JcQy9QPCdBMBpEMh1INh9INh5ENhoAXDwsYUEuZEk1VD8pVUIkXEsqdGBBg25FjXRFaV5Nbmxt6ufhl5J6dW8yfXY6eHE3dW43bmgvamUpZmYpAICEQYiMSJ6ZWZqWVLKxgNTRu97e0szLtcO+m5iShZWPitzW1a2vjI6XRpWZTo+WSoeNRH+EP39/PoJ8RACUmE+amlahn1fBvoq2sKm/vMDOzc7l5ea9vMFFPjxgVUl2bGlvZUukm12gnFqVl1CNjkqNiUx+g0J5gkAAnINelntVrJ11wK+Tjod/aGFesays6uThqqemi4iIVU5KeW5hnYtcnIhZn4hek4NUgHBGf25JdGk+bmk5AI+CUJqKXqiadaSUbnx1a25jRlZNOGFfWpaTlqujoGZdVMO3nqmdXqGVWqGWVpuOVZWGUYd4S31zQG9wMQCVkUqooV2qm12nn3+Wj2iJgFyHe0ackFSWjoKjmot/c0i7tKmsnGicjFSiklemj1yVhFGRf1ODd0dzei0AkJFDkJA9oJhgkYtck5BFhn1Qr6laoZZQtqyGt7CWnZNHr6aI08mzmIRSl4VUj3xOinhMhHRKdmpCYmouAIqFQ46JQZSMUpmOUJOOVJWOSqSeT5+TUKqZZbKomK6dYqiZVdDHr6+bc5aJSYN7Tlx1ilh2kl16mW10UQCCdUWHfkWYj1CQiEqIe1OVhVOMgUqbi1mUhkvIvqOyqH6Xi0mPg1CLf0yKfUGBeE5QdqJWga5mirGJgmUAgX83dHYtfHs0gXg+f3c7hYI8e3wzg3lDgHc7mpRhx76jhXpAfnQ8gHRBdXA0fHc0dX1mZoOeXHqUd3I4AHp/LHx8Mn97OYF5PoB/NoaDOoiCPod+QpCLQomCP5eOVZCMQ4V/PHx0Onl1NHt6MnZzInJzV2drV2trG5RmVzhAXQ1HAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/e864253c01617691383cd1762ddb91db/507e6/2024-09-13-cover.webp 200w,
/static/e864253c01617691383cd1762ddb91db/28a80/2024-09-13-cover.webp 400w,
/static/e864253c01617691383cd1762ddb91db/db9b4/2024-09-13-cover.webp 750w&quot;
              sizes=&quot;(max-width: 750px) 100vw, 750px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/e864253c01617691383cd1762ddb91db/36ca5/2024-09-13-cover.png 200w,
/static/e864253c01617691383cd1762ddb91db/a3397/2024-09-13-cover.png 400w,
/static/e864253c01617691383cd1762ddb91db/b13e1/2024-09-13-cover.png 750w&quot;
            sizes=&quot;(max-width: 750px) 100vw, 750px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/e864253c01617691383cd1762ddb91db/b13e1/2024-09-13-cover.png&quot;
            alt=&quot;2024 09 13 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When you think upfront and want to make things right, there’s an interesting feedback loop. Quite often, things start to click, often in a surprising way.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I recently wrote on &lt;a href=&quot;/en/talk-is-cheap-show-me-the-numbers/&quot;&gt;Architecture Weekly about my performance investigations&lt;/a&gt; in &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt; and &lt;a href=&quot;https://event-driven-io.github.io/Pongo/getting-started.html&quot;&gt;Pongo&lt;/a&gt;.&lt;/strong&gt; One of the conclusions was that schema needs to be generated upfront. Initially, it was generated once on the first call. That reduced boilerplate and was good enough for many cases but not for serverless.&lt;/p&gt;
&lt;p&gt;To generate the &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt; PostgreSQL schema, I also wanted to be able to generate it for &lt;a href=&quot;https://event-driven-io.github.io/Pongo/getting-started.html&quot;&gt;Pongo&lt;/a&gt; documents that I use for read models.&lt;/p&gt;
&lt;p&gt;Pongo documents are stored in collections, and collections are regular (well, almost) PostgreSQL tables. So, to know what to generate, I had to add some way to know what collections I’ll have. There’s no such API in vanilla Mongo, so I have to add it. And that’s fine, as I want to make Pongo a superset of Mongo.&lt;/p&gt;
&lt;p&gt;My initial idea was to provide a list of collections with names. This list could later contain a JSON schema definition, database indexes, etc. I also had to add an option to provide the database list. Pongo (just like Mongo) allows different dbs to be used.&lt;/p&gt;
&lt;p&gt;The naive version could look like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; schema &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;postgres&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; collections&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;users&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;orders&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; client &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;postgresConnectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  schema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; definition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; schema &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That would give me the information I need to set up users and order tables for collections in the default Postgres database. I could call it a day, but…&lt;/p&gt;
&lt;p&gt;But then I thought, well, wouldn’t it be nice to generate a strongly typed TypeScript client? Having schema makes that possible! I “just” have to use a sneaky feature like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy&quot;&gt;Proxy type&lt;/a&gt;. So I did, and bang, here we are with the new release!&lt;/p&gt;
&lt;h2 id=&quot;strongly-typed-client&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#strongly-typed-client&quot; aria-label=&quot;strongly typed client permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Strongly Typed Client&lt;/h2&gt;
&lt;p&gt;The API needs to be a bit more advanced, but I think it’s still straightforward and explicit. You need to define schema like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  _id&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  address&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Address&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  tags&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Customer&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  _id&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  address&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Address&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; schema &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoSchema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  database&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; pongoSchema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    users&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; pongoSchema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;users&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    customers&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; pongoSchema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Customer&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;customers&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And pass it to the client, getting the typed version.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; typedClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;postgresConnectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  schema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; definition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; schema &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// 👇 client have the same database as we defined above, and the collection&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; typedClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;database&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; doc&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; User &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;randomUUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Anita&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; inserted &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;doc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// 👇 yup, the collection is fully typed!&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongoDoc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Anita&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I think that’s much better developer experience, than the Mongo API that tells us always to do calls like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; db  &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; client&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;postgres&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Of course, if you like it, you can still use it. It’s great to have more options!&lt;/p&gt;
&lt;p&gt;Internally, it generates the collections upfront and assigns them to the typed properties. If you want to know how that works internally, reply to this article, and I can explain how sausages are made in the follow-up!&lt;/p&gt;
&lt;h3 id=&quot;pongo-gets-command-line&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pongo-gets-command-line&quot; aria-label=&quot;pongo gets command line permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pongo gets command line&lt;/h3&gt;
&lt;p&gt;And we’re getting back to the announced synergy between making things right. Having schema also enabled upfront schema generation and even migration. To make that accessible, I added command line tooling.&lt;/p&gt;
&lt;p&gt;You can either install it globally through:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; -g @event-driven-io/pongo&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And run it with:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pongo&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;or without installing it globally by using &lt;a href=&quot;https://docs.npmjs.com/cli/v8/commands/npx&quot;&gt;npx&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx @event-driven-io/pongo&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cool, but what do you get from it?&lt;/p&gt;
&lt;h2 id=&quot;sample-configuration-generation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#sample-configuration-generation&quot; aria-label=&quot;sample configuration generation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Sample configuration generation&lt;/h2&gt;
&lt;p&gt;You can generate the sample config by calling:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx @event-driven-io/pongo config sample --generate --file ./src/pongoConfig.ts --collection &lt;span class=&quot;token function&quot;&gt;users&lt;/span&gt; --collection orders&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This command will create a config file in the selected location with predefined users and orders collections. It’ll look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pongoSchema &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/pongo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; date&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; description&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; date&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  schema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; pongoSchema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    database&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; pongoSchema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      users&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; pongoSchema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;users&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      orders&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; pongoSchema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Order&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;orders&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Or just print it with:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx @event-driven-io/pongo config sample --print --collection &lt;span class=&quot;token function&quot;&gt;users&lt;/span&gt; --collection customers&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, you can use adjust the generated typing and import it to your application.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pongoClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/pongo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; config &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;./pongo.config&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  schema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; definition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; config&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;schema &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;performing-database-migrations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#performing-database-migrations&quot; aria-label=&quot;performing database migrations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Performing Database Migrations&lt;/h3&gt;
&lt;p&gt;Having the existing configuration file and command-line tooling opens even more options. You not only get a strongly typed client but also can generate and perform migrations based on it!&lt;/p&gt;
&lt;p&gt;You can do it with new command line tooling:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx @event-driven-io/pongo migrate run --config ./dist/pongoConfig.js &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
--connectionString postgresql://postgres:postgres@localhost:5432/postgres&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’ll automatically run the migrations based on the defined collections.&lt;/p&gt;
&lt;p&gt;If you’re unsure and don’t trust it fully, you can also add the &lt;em&gt;—-dryRun&lt;/em&gt; parameter. This will run the migration in the transaction and roll it back without making any changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can also use migration CLI in your build pipelines.&lt;/strong&gt; You might not want to pass the connection string there, as it’s not secured way. No worries, you can also set &lt;em&gt;DB_CONNECTION_STRING&lt;/em&gt; environment variable and run it  as&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx @event-driven-io/pongo migrate run --config ./dist/pongoConfig.js&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can also run it by providing a collections list:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx @event-driven-io/pongo migrate run --collection &lt;span class=&quot;token function&quot;&gt;users&lt;/span&gt; --collection customers &lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;
--connectionString postgresql://postgres:postgres@localhost:5432/postgres&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can also just print migrations to see what schema structures will be generated by calling:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;npx @event-driven-io/pongo migrate sql --print --collection &lt;span class=&quot;token function&quot;&gt;users&lt;/span&gt; --collection customers&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It will print:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; migrations &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    id &lt;span class=&quot;token keyword&quot;&gt;SERIAL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    name &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;UNIQUE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    application &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;default&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    sql_hash &lt;span class=&quot;token keyword&quot;&gt;VARCHAR&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;timestamp&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TIMESTAMP&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;CURRENT_TIMESTAMP&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; users &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      _id           &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;           &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
      &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;          JSONB          &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
      metadata      JSONB          &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      _version      &lt;span class=&quot;token keyword&quot;&gt;BIGINT&lt;/span&gt;         &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      _partition    &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;           &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;png_global&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      _archived     &lt;span class=&quot;token keyword&quot;&gt;BOOLEAN&lt;/span&gt;        &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;FALSE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      _created      TIMESTAMPTZ    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      _updated      TIMESTAMPTZ    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; customers &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      _id           &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;           &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
      &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;          JSONB          &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
      metadata      JSONB          &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      _version      &lt;span class=&quot;token keyword&quot;&gt;BIGINT&lt;/span&gt;         &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      _partition    &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;           &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;png_global&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      _archived     &lt;span class=&quot;token keyword&quot;&gt;BOOLEAN&lt;/span&gt;        &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;FALSE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      _created      TIMESTAMPTZ    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      _updated      TIMESTAMPTZ    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This first &lt;em&gt;migrations&lt;/em&gt; table is essential, as it keeps all the migrations running so far. So, if you run the migration CLI once, it’ll only run the migrations once. Migrations are using internally &lt;a href=&quot;https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS&quot;&gt;Postgres Advisory Locks&lt;/a&gt; to ensure that no migrations are happening in parallel. Safety first!&lt;/p&gt;
&lt;p&gt;In the future, you’ll also be able to provide your custom schema and data migrations through it!&lt;/p&gt;
&lt;p&gt;You already got &lt;a href=&quot;https://github.com/event-driven-io/Pongo/blob/c3ed330a3ddf7793e6d508309cd2f729c6b19cb2/src/packages/dumbo/src/core/schema/schemaComponent.ts&quot;&gt;Schema Components abstraction&lt;/a&gt;. They define the database schema as a tree structure. They’re used for database collection, allowing migration through code. They’re exposed in the schema property. In the longer term, it’ll be possible to add your own, like indexes, migrations, etc.&lt;/p&gt;
&lt;h3 id=&quot;added-possibility-to-disable-generating-pongo-schema-upfront&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#added-possibility-to-disable-generating-pongo-schema-upfront&quot; aria-label=&quot;added possibility to disable generating pongo schema upfront permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Added possibility to disable generating Pongo schema upfront&lt;/h3&gt;
&lt;p&gt;And we’re getting to performance. It appears that running schema migrations automatically is an excellent developer experience but not ideal for regular deployment. Surprise!&lt;/p&gt;
&lt;p&gt;Now, thanks to the schema and CLI tooling for migrations, you can run migrations manually (or through the build process) and can ignore the automated migration in the Pongo client and get the performance boost:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; typedClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;postgresConnectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  schema&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; autoMigration&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;None&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; definition&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; schema &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will disable any automated schema generation. As a result, your application will have fewer database calls, opened connections, and overhead!&lt;/p&gt;
&lt;h3 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;TLDR&lt;/h3&gt;
&lt;p&gt;The need for those changes appeared unexpectedly. I had to improve the performance for non-pooled connections (e.g., in a serverless environment). I could have done a quick patch and called it a day, but I did a sanity check and rethought that a bit. That led to a bit more work but also surprising synergy and opening more options for the future.&lt;/p&gt;
&lt;p&gt;I’m pretty happy about that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I think that’ll also boost the developer experience even more!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Expect the follow up in Emmett.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are your thoughts?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Read more about building Emmett and Pongo in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/introducing_emmett/&quot;&gt;Announcing Emmett! Take your event-driven applications back to the future!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/introducting_pongo/&quot;&gt;Pongo - Mongo but on Postgres and with strong consistency benefits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/emmett_postgresql_event_store/&quot;&gt;Event Sourcing on PostgreSQL in Node.js just became possible with Emmett&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/introducing_emmett/&quot;&gt;Testing Event Sourcing, Emmett edition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/emmett_projections_testing/&quot;&gt;Writing and testing event-driven projections with Emmett, Pongo and PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/projections_and_event_metadata/&quot;&gt;Using event metadata in event-driven projections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/custom_test_container_on_esdb_example/&quot;&gt;How to configure a custom Test Container on the EventStoreDB example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/inmemory_message_bus_in_typescript/&quot;&gt;How to build an in-memory Message Bus in TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check also the &lt;a href=&quot;https://github.com/event-driven-io/Pongo/blob/c3ed330a3ddf7793e6d508309cd2f729c6b19cb2/samples/simple-ts/src/typedClient.ts&quot;&gt;sample&lt;/a&gt; and &lt;a href=&quot;https://github.com/event-driven-io/Pongo/releases/tag/0.14.0&quot;&gt;release notes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Show me the money! Practically navigating the Cloud Costs Complexity]]></title><description><![CDATA[In my last article, I explored how Amazon S3’s new conditional writes feature can be used to implement a strongly consistent event store. I…]]></description><link>https://event-driven.io/en/show-me-the-money-practically-navigating/</link><guid isPermaLink="false">https://event-driven.io/en/show-me-the-money-practically-navigating/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;/en/using-s3-but-not-the-way-you-expected/&quot;&gt;In my last article, I explored how Amazon S3’s new conditional writes feature can be used to implement a strongly consistent event store.&lt;/a&gt;&lt;/strong&gt; I think that this feature opens up powerful architectural possibilities for distributed systems. But as much as we’d love to dive right into the code, there’s a bigger question to answer first: how much will it cost?&lt;/p&gt;
&lt;p&gt;This article cannot be started in any other way than one of my favourite movie scenes:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/IpwSXWq1wwU?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Show you the money! Not you, show me the money!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We’ve all seen cloud bills get out of hand, often because the true infrastructure costs are harder to predict than they seem at first glance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Today, we’ll grab a calculator to discuss the costs of building an event store on S3.&lt;/strong&gt; I’m not an expert here; there are smarter and more skilled people than me. It might be that you’re one of them.&lt;/p&gt;
&lt;p&gt;That’s why I’m counting on your feedback, not only the money. I will show you the money and me the money so you can see how I typically calculate, manage, and optimize the costs in the Cloud. That can be food for thought.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You’ll see that this is not your typical “pay for storage and requests” breakdown.&lt;/strong&gt; We’ll examine the specific challenges of request patterns, working set size, DELETE operations, and load spikes and how to make smarter decisions based on AWS pricing models. I’ll use AWS, but similar calculations can and should be done for other cloud providers.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;using-s3-conditional-writes-for-event-stores&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#using-s3-conditional-writes-for-event-stores&quot; aria-label=&quot;using s3 conditional writes for event stores permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Using S3 Conditional Writes for Event Stores&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Before we dive into the math equation, an &lt;a href=&quot;/en/event_stores_are_key_value_stores/&quot;&gt;event store is a key-value database&lt;/a&gt; where all business operations results are recorded as immutable events.&lt;/strong&gt; A traditional record is represented as a sequence of events called an event stream. Each operation loads all events from the stream, builds the state in memory and appends a new fact-event.&lt;/p&gt;
&lt;p&gt;As event stores are databases, they should give strong consistency guarantees like supporting &lt;a href=&quot;/en/optimistic_concurrency_for_pessimistic_times/&quot;&gt;optimistic concurrency&lt;/a&gt;. The new conditional writes feature was triggered if S3 can give such guarantees now, and it can! The &lt;em&gt;If-None-Match&lt;/em&gt; header support in S3 ensures that only a single event can be appended with a specific stream (record) version.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;If-None-Match&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;header works only during the file creation, we need the following naming schema (or similar) to guarantee that:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{streamPrefix}/{streamtType}/{streamId}/{streamVersion}.{chunkVersion}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Effectively, the new event is a new file in the S3 bucket. And S3 is not optimised for such usage, as it favours smaller amounts of bigger files. We discussed how to overcome that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The key design is around active chunks.&lt;/strong&gt; In this system, new events are written to a chunk (file) that remains active while being appended. A chunk can contain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;one or more events as a result of business operations,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;stream metadata (id, version, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;snapshot - full representation of our entity/aggregate.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Replaying all events from the beginning to rebuild an entity’s state can be costly regarding GET requests (as we’re paying for each request). To reduce this, the design incorporates snapshots, which store a full representation of the current state within a chunk. By fetching the latest snapshot, the system can avoid replaying the entire event history, minimizing the number of GET operations.&lt;/p&gt;
&lt;p&gt;We also need to pay for PUT operation to append each event.&lt;/p&gt;
&lt;p&gt;As each chunk will be named with an autoincremented stream version, we must pay for LIST to find the latest one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We also discussed compacting event stream data to reduce storage costs.&lt;/strong&gt; Chunks can be compacted periodically, meaning old events are merged and unnecessary chunks are deleted, further lowering storage and request costs. Sealed chunks can be deleted or moved to lower-cost storage tiers like S3 Intelligent-Tiering or S3 Glacier, reducing storage costs.&lt;/p&gt;
&lt;p&gt;This approach leverages S3’s conditional writes to ensure consistency and manages costs by strategically using snapshots, chunking, and storage tiers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So ok, how much will it cost?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;basic-costs-calculations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#basic-costs-calculations&quot; aria-label=&quot;basic costs calculations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Basic costs calculations&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;When designing an event-sourced system, it’s easy to assume that keeping track of system changes is a cheap and neglectable cost nowadays.&lt;/strong&gt; You just log some events and store them, right? But what happens when your event payloads grow larger than expected? Suddenly, those small changes to event size can greatly impact your storage costs. Let’s break down three scenarios, starting with a common size of the events, then looking at what happens if things go wrong.&lt;/p&gt;
&lt;h3 id=&quot;4kb-events-the-lean-and-efficient-system&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#4kb-events-the-lean-and-efficient-system&quot; aria-label=&quot;4kb events the lean and efficient system permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;4KB Events: The Lean and Efficient System&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Let’s start with an efficient design. In this system, each event logs essential information about an &lt;strong&gt;insurance claim&lt;/strong&gt;—claim creation, adjustments, approvals, and payments. It includes the necessary details like timestamps, customer info, and claim data. In such a case, 4KB on average should be enough to also keep snapshot and stream metadata (as snapshots should be trimmed to keep only data used in business logic; they don’t need complete information).&lt;/p&gt;
&lt;p&gt;Here’s what the system could look like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;500,000 active insurance policies&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each policy generates &lt;strong&gt;1 claim per year&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Each claim has &lt;strong&gt;ten events&lt;/strong&gt; (e.g., submission, review, payment).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Total: &lt;strong&gt;5 million events per year&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1000 &lt;strong&gt;GET&lt;/strong&gt; Requests: &lt;strong&gt;$0,0004&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;1000 &lt;strong&gt;POST/PUT/DELETE/LIST&lt;/strong&gt; Requests: &lt;strong&gt;$0,005&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;costs-for-4kb-events&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#costs-for-4kb-events&quot; aria-label=&quot;costs for 4kb events permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Costs for 4KB Events&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Let’s ignore the free tier and other promotions (for now). The costs could look as follows.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Storage&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;5 million events per year × 4KB = &lt;strong&gt;20GB of data per year&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;417 thousand events per year × 4KB = &lt;strong&gt;1,67 GB of icrement data per month&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;S3 Standard storage: &lt;strong&gt;$0.023 per GB per month&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Monthly increment cost: &lt;strong&gt;$0.038&lt;/strong&gt; &lt;em&gt;(storage cost of new events within a month)&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Total accumulated yearly storage cost: &lt;strong&gt;$2.99&lt;/strong&gt; &lt;em&gt;(we only pay for what we accumulate: 16.67 GB in the first month, 33.33 GB in the second, etc)&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Requests&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;5 million GET requests&lt;/strong&gt; (one per event) = &lt;strong&gt;$2&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;5 million PUT requests&lt;/strong&gt; (one per event) = &lt;strong&gt;$25&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;5 million LIST requests&lt;/strong&gt; (one per event) = &lt;strong&gt;$25&lt;/strong&gt; (we need it to find the active stream chunk)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Total Year 1 Costs&lt;/strong&gt;:&lt;br&gt;
Storage: &lt;strong&gt;$2.99&lt;/strong&gt;&lt;br&gt;
Requests: &lt;strong&gt;$52&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Total&lt;/strong&gt;: &lt;strong&gt;$54,99&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;40kb-events-things-start-to-escalate&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#40kb-events-things-start-to-escalate&quot; aria-label=&quot;40kb events things start to escalate permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;40KB Events: things start to escalate&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;While 4KB events are a more reasonable size for most event sourcing systems, it’s possible that due to poor design, additional (meta)data, or overly verbose data formats, events could balloon to 40KB. I wrote about this in &lt;a href=&quot;/en/i_will_just_add_one_more_field/&quot;&gt;Anti-patterns in event modelling - I’ll just add one more field&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;costs-for-40kb-events&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#costs-for-40kb-events&quot; aria-label=&quot;costs for 40kb events permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Costs for 40KB Events&lt;/strong&gt;&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Storage&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;5 million events per year × 4KB = &lt;strong&gt;20GB of data per year&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;417 thousand events per year × 4KB = &lt;strong&gt;1,67 GB of icrement data per month&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;S3 Standard storage: &lt;strong&gt;$0.023 per GB per month&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Monthly increment cost: &lt;strong&gt;$0.38&lt;/strong&gt; &lt;em&gt;(storage cost of new events within a month)&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Total accumulated yearly storage cost: &lt;strong&gt;$29.90&lt;/strong&gt; &lt;em&gt;(we only pay for what we accumulate: 16.67 GB in the first month, 33.33 GB in the second, etc)&lt;/em&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Requests&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;5 million GET requests&lt;/strong&gt; (one per event) = &lt;strong&gt;$2&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;5 million PUT requests&lt;/strong&gt; (one per event) = &lt;strong&gt;$25&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;5 million LIST requests&lt;/strong&gt; (one per event) = &lt;strong&gt;$25&lt;/strong&gt; (we need it to find the active stream chunk)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Total Year 1 Costs&lt;/strong&gt;:&lt;br&gt;
Storage: &lt;strong&gt;$29.90&lt;/strong&gt;&lt;br&gt;
Requests: &lt;strong&gt;$52&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Total&lt;/strong&gt;: &lt;strong&gt;$81,90&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It’s visible that the request costs stay the same, and that’s a significant power of S3. You don’t pay the transfer cost as long as you stay inside the AWS network.&lt;/p&gt;
&lt;p&gt;Still, it’s visible that storage costs went ten times higher, going above the request cost. Let’s check the next scenario.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;400kb-events-the-worst-case-scenario&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#400kb-events-the-worst-case-scenario&quot; aria-label=&quot;400kb events the worst case scenario permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;400KB Events: The Worst-Case Scenario&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Now, let’s assume something went &lt;strong&gt;wrong&lt;/strong&gt; in the design process. Instead of storing references to large documents (like PDFs or images), someone &lt;strong&gt;included&lt;/strong&gt; the entire file &lt;strong&gt;within each event&lt;/strong&gt;. The event size skyrockets to &lt;strong&gt;400KB&lt;/strong&gt;—an inefficient bloat.&lt;/p&gt;
&lt;p&gt;As with S3, math is relatively simple; we can multiply previous results by 10 and get:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Total Year 1 Costs&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Total Storage: 200GB&lt;br&gt;
Storage: &lt;strong&gt;$299&lt;/strong&gt;&lt;br&gt;
Requests: &lt;strong&gt;$52&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Total&lt;/strong&gt;: &lt;strong&gt;$351&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now, the storage costs skyrocketed.&lt;/p&gt;
&lt;h2 id=&quot;optimising-storage-costs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#optimising-storage-costs&quot; aria-label=&quot;optimising storage costs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Optimising storage costs&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;The key lesson is that storage costs grow with event size, but request costs stay flat.&lt;/strong&gt; Also, the event number adds to both the request and storage costs.&lt;/p&gt;
&lt;p&gt;Notice that the request costs remain the same no matter how much the event size increases. AWS charges for requests based on the number of PUT, GET or LIST operations, not the size of the data being sent or retrieved. This means your &lt;strong&gt;request costs remain flat&lt;/strong&gt;, but your &lt;strong&gt;storage costs balloon&lt;/strong&gt; as your event size grows.&lt;/p&gt;
&lt;p&gt;If you stick with 4KB events, storage costs are tiny. However, as we saw in the 40KB and 400KB examples, larger events can increase your storage costs by 10x or even 100x.&lt;/p&gt;
&lt;p&gt;Here are the things we can learn from it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bad Design Leads to Huge Cost Jumps.&lt;/strong&gt; What if you miscalculate or don’t optimize your event sizes early on? A seemingly small design oversight—like storing entire PDFs or unnecessary metadata within the event—can turn into a massive cost later. Going from 40KB to 400KB isn’t just bad practice; it’s a 100x increase in storage costs. This is why it’s critical to validate event sizes early and often.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ask the Business for Real-World Examples.&lt;/strong&gt; Before designing your event-sourced system, get real-world data from the business. What kind of events are you storing? How much data really needs to be captured? Without examples, you might overestimate how much data each event needs to carry, which could lead to inflated storage costs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use the Claim Check Pattern for Large Files.&lt;/strong&gt; In cases where you need to store large files (e.g., PDFs or high-resolution images for an insurance claim), you should never store them directly inside the event. Instead, use the Claim Check Pattern. Store the file in a separate S3 bucket and include a reference (such as a URL or ID) in the event. This keeps your event payloads small and your storage costs under control.&lt;/p&gt;
&lt;p&gt;For example, instead of embedding a &lt;strong&gt;400KB PDF&lt;/strong&gt; in the event, store the PDF in a separate bucket and add a link like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;{
  &quot;document_url&quot;: &quot;s3://insurance-bucket/claim12345/document.pdf&quot;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This approach dramatically reduces the size of your events and saves you from the inflated costs we saw in the &lt;strong&gt;400KB scenario&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Never Underestimate the Impact of Event Size.&lt;/strong&gt; Event size is more than a technical detail—it’s a cost driver that can make or break your budget. A well-designed system with small events will keep costs low, but if you let event sizes balloon due to poor design choices, your storage costs can spiral out of control.&lt;/p&gt;
&lt;p&gt;That’s okay, but what do we do about that? What if we choose a data storage format other than JSON?&lt;/p&gt;
&lt;h3 id=&quot;dropping-json-format&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#dropping-json-format&quot; aria-label=&quot;dropping json format permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Dropping JSON format&lt;/h3&gt;
&lt;p&gt;While we’ve been using JSON for our event storage examples so far, it’s important to understand that JSON, while widely used, isn’t always the most optimal format for storage efficiency and performance. Let’s break down why JSON is commonly chosen and why you might consider switching to something more efficient, like &lt;a href=&quot;https://parquet.apache.org/&quot;&gt;Parquet&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;why-json-is-popular&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-json-is-popular&quot; aria-label=&quot;why json is popular permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Why JSON is Popular&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;JSON is often the default choice for event sourcing because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Human-readable&lt;/strong&gt;: It’s easy to read and write, which makes debugging and inspecting data simple.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flexible&lt;/strong&gt;: It can handle nested structures (like objects within objects) without much effort, making it a good choice for complex event payloads.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Widely supported&lt;/strong&gt;: Almost every modern language, framework, and tool can handle JSON.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, &lt;strong&gt;JSON comes with significant downsides&lt;/strong&gt;, especially when it comes to &lt;strong&gt;storage size&lt;/strong&gt; and &lt;strong&gt;efficiency&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Inefficient use of space&lt;/strong&gt;: JSON stores data in a very lengthy manner. Every field name is repeated for each event, and there’s no compression, which leads to &lt;strong&gt;significant storage bloat&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Slow to parse&lt;/strong&gt;: JSON can be slow to read and write at scale, especially when dealing with large event payloads.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;parquet-a-more-efficient-format&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#parquet-a-more-efficient-format&quot; aria-label=&quot;parquet a more efficient format permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Parquet: A More Efficient Format&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;On the other hand, Parquet is a columnar storage format highly optimized for both storage efficiency and query performance.&lt;/strong&gt; Parquet is especially useful when dealing with large datasets and doesn’t need the events to be human-readable. Here’s why you might want to use Parquet instead:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Compressed and space-efficient&lt;/strong&gt;: Parquet compresses your data, which can &lt;strong&gt;significantly reduce storage costs&lt;/strong&gt;. You often see &lt;strong&gt;3x to 10x&lt;/strong&gt; reductions in storage compared to JSON.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optimized for queries&lt;/strong&gt;: Since Parquet is a columnar format, it’s very fast to query specific fields across large datasets. This makes it perfect for analytics or batch processing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Great for structured data&lt;/strong&gt;: If your events follow a consistent structure, Parquet excels at storing them compactly and efficiently.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, Parquet has its downsides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Not human-readable&lt;/strong&gt;: Unlike JSON, Parquet is not easy to inspect manually, which makes it more difficult to debug or directly read the data without using specific tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Overhead for small files&lt;/strong&gt;: Parquet is optimized for large-scale data. Using it for small, individual event files can introduce overhead, so it’s best to batch events together or handle large data sets in a query-friendly way.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See also example benchmarks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/kzzzr/837645284f8dfe879193997fa9632a65&quot;&gt;Artemiy Kzr - File formats comparison: CSV, JSON, Parquet, ORC&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/norberttech/fa00afa021520be34299f3a2b8f164b3?permalink_comment_id=4752698&quot;&gt;Norbert Orzechowicz - Parquet vs Json - PHP Benchmark&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://medium.com/data-engineering-chariot/athena-performance-comparison-avro-json-and-parquet-2b13c5090cc&quot;&gt;Athena Performance Comparison: Avro, JSON, and Parquet&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Still, our data is structured; we don’t need to make it human-readable (if we do, we can use, e.g. &lt;a href=&quot;https://duckdb.org/docs/extensions/httpfs/s3api.html&quot;&gt;DuckDb S3 with Parquet support&lt;/a&gt;). We need to cut costs, right?!&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;redoing-the-cost-analysis-with-parquet-format&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#redoing-the-cost-analysis-with-parquet-format&quot; aria-label=&quot;redoing the cost analysis with parquet format permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Redoing the Cost Analysis with Parquet Format&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Let’s now see how switching from JSON to Parquet affects your costs. For simplicity, we’ll assume that Parquet offers 5x compression compared to JSON, which is a typical figure when dealing with structured data.&lt;/p&gt;
&lt;p&gt;Let’s do some math again. The request cost will stay the same (obviously), but the storage cost will become five times smaller:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4KB events - Total Year 1 Costs&lt;/strong&gt;:&lt;br&gt;
Storage: &lt;strong&gt;$0,60&lt;/strong&gt;&lt;br&gt;
Requests: &lt;strong&gt;$52&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Total&lt;/strong&gt;: &lt;strong&gt;$52,60&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;40KB events - Total Year 1 Costs&lt;/strong&gt;:&lt;br&gt;
Storage: &lt;strong&gt;$5,98&lt;/strong&gt;&lt;br&gt;
Requests: &lt;strong&gt;$52&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Total&lt;/strong&gt;: &lt;strong&gt;$57,98&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;400KB events - Total Year 1 Costs&lt;/strong&gt;:&lt;br&gt;
Storage: &lt;strong&gt;$59,80&lt;/strong&gt;&lt;br&gt;
Requests: &lt;strong&gt;$52&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Total&lt;/strong&gt;: &lt;strong&gt;$111,80&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Switching to Parquet for your event storage provides significant cost savings, especially when dealing with large event payloads.&lt;/strong&gt; The storage cost reduction is dramatic because Parquet compresses data efficiently, and the larger the payload, the more you save. This should also decrease the costs as time goes.&lt;/p&gt;
&lt;p&gt;It sounds like we have a winner! Ok, but didn’t we forget about something?&lt;/p&gt;
&lt;h2 id=&quot;hot-warm-and-cold-storage-why-it-matters&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#hot-warm-and-cold-storage-why-it-matters&quot; aria-label=&quot;hot warm and cold storage why it matters permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Hot, Warm, and Cold Storage: Why It Matters&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Ok, so we understand the basic costs from our previous analysis, and it’s clear that storage costs are one of the highest factors to manage. Let’s now explore optimising those costs by leveraging different S3 storage tiers. But before we get into the specific strategies for our case, let’s first explain the concept of &lt;strong&gt;hot, warm, and cold storage and why it matters in (cloud) architecture.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We can categorise data into hot, warm, and cold storage based on access frequency and retrieval speed. Understanding this categorization is crucial for optimizing both &lt;strong&gt;performance&lt;/strong&gt; and &lt;strong&gt;cost&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hot Storage&lt;/strong&gt; is the storage tier for frequently accessed data, usually for business logic and UI. It requires low-latency retrieval and updates, which is essential for real-time data. Because of its low latency needs and frequent updates, it usually costs the most.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Warm Storage&lt;/strong&gt;: Warm storage is a middle ground for data accessed occasionally but not frequently enough to justify the high cost of hot storage. It balances cost and retrieval speed, making it ideal for data that are needed periodically but not constantly. This can be data kept for legal reasons (e.g. it won’t be typically updated, but we should allow someone to do it).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cold Storage&lt;/strong&gt;: This tier is for infrequently accessed data, such as archives or backup data, needed only for audits or legal compliance. Cold storage is the cheapest option but comes with longer retrieval times and slower performance.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Why It’s Important&lt;/strong&gt;: By segmenting your data into these storage tiers, you can significantly reduce your costs without compromising on the performance where it’s needed. Let’s see how these concepts apply to our specific &lt;strong&gt;event-sourced system&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;stream-compaction-further-reduce-costs&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#stream-compaction-further-reduce-costs&quot; aria-label=&quot;stream compaction further reduce costs permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Stream Compaction Further Reduce Costs&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In the previous article, we introduced the concept of &lt;strong&gt;active chunks&lt;/strong&gt; in the context of our event-sourced system built on S3.&lt;/p&gt;
&lt;p&gt;Here’s how it works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Active Chunk&lt;/strong&gt;: Each time a new event is appended, it is written to an &lt;strong&gt;active chunk&lt;/strong&gt;. This chunk is regularly accessed for both reads and writes, so it needs to be stored in a tier that supports fast access. It remains “active” as long as new events are added.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sealed Chunks&lt;/strong&gt;: Once an active chunk is &lt;strong&gt;sealed&lt;/strong&gt; (i.e., a new active chunk is added), it becomes less frequently accessed. However, it may still be accessed if we need to replay historical events (for example, to rebuild the state after a snapshot is invalidated). Sealed chunks are the perfect candidates to move to warm storage, where the cost is lower, but access is still reasonably fast.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Archived Chunks&lt;/strong&gt;: Over time, chunks that are never accessed (e.g., past data needed only for legal or audit purposes) can be moved to &lt;strong&gt;cold&lt;/strong&gt; storage. This allows us to archive the data at the lowest possible cost while keeping it available.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As we manage the event streams, chunks grow over time. This can lead to increased costs without proper strategies due to the volume of GET requests required to replay events or retrieve historical data. &lt;strong&gt;To address this, we can use a technique called stream compaction (or tombstoning).&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I talked about it in:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/7NGlYgobTyY?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;And explained in my article: &lt;a href=&quot;/en/gdpr_in_event_driven_architecture/&quot;&gt;How to deal with privacy and GDPR in Event-Driven systems&lt;/a&gt;. But let’s give a short TLDR.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stream compaction&lt;/strong&gt; refers to merging multiple older chunks into one larger chunk. This reduces the number of objects stored in S3, leading to fewer GET requests when accessing historical data.&lt;/p&gt;
&lt;p&gt;In some business contexts, such as financial data or &lt;strong&gt;claims processing&lt;/strong&gt;, we can use a &lt;strong&gt;Closing the Books&lt;/strong&gt; strategy to summarize the state of a stream at regular intervals (e.g., annually). After this summary, new events are kept only for the next time period, reducing the amount of historical data needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Instead of keeping all historical data accessible, you can summarize older data and archive it, reducing both storage costs and access costs.&lt;/strong&gt; We could create a summary of the claim at the end of each fiscal year. Then, we can move the data from the previous year into cold storage and only access it if an audit or a legal request is made.&lt;/p&gt;
&lt;h3 id=&quot;cost-optimization-using-s3-storage-tiers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cost-optimization-using-s3-storage-tiers&quot; aria-label=&quot;cost optimization using s3 storage tiers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Cost Optimization Using S3 Storage Tiers&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Let’s explore each chunk type’s different S3 storage tiers (active, sealed, and archived) and calculate the potential savings.&lt;/p&gt;
&lt;h4 id=&quot;1-hot-storage-active-chunks-in-s3-standard&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-hot-storage-active-chunks-in-s3-standard&quot; aria-label=&quot;1 hot storage active chunks in s3 standard permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;1. Hot Storage: Active Chunks in S3 Standard&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Active chunks must be kept in &lt;strong&gt;S3 Standard&lt;/strong&gt; because they require &lt;strong&gt;fast, low-latency access&lt;/strong&gt; for frequent writes and reads. Storing active chunks in S3 Standard ensures that our system remains responsive during real-time operations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cost&lt;/strong&gt;: $0.023 per GB/month.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access&lt;/strong&gt;: Low-latency, frequent reads and writes.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Assume that our active chunk holds &lt;strong&gt;200GB&lt;/strong&gt; of data at any time.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage cost&lt;/strong&gt;: 100GB × $0.023 = &lt;strong&gt;$4.60/month&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This cost is manageable for the active chunk, but if the data remained in S3 Standard long-term, it would become inefficient. That’s why moving sealed chunks to more cost-effective tiers is critical.&lt;/p&gt;
&lt;h4 id=&quot;2-warm-storage-sealed-chunks-in-s3-intelligent-tiering&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-warm-storage-sealed-chunks-in-s3-intelligent-tiering&quot; aria-label=&quot;2 warm storage sealed chunks in s3 intelligent tiering permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;2. Warm Storage: Sealed Chunks in S3 Intelligent-Tiering&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Once a chunk is sealed, we can reduce costs by moving it to &lt;strong&gt;S3 Intelligent-Tiering&lt;/strong&gt;. Intelligent-Tiering automatically shifts data between the frequent and infrequent access tiers based on usage patterns. It helps lower costs for data that isn’t accessed often but still provides reasonable access speeds.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cost&lt;/strong&gt;: $0.023 per GB for frequent access and $0.0125 per GB for infrequent access.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access&lt;/strong&gt;: Automatic tiering depending on access patterns.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Assume you have &lt;strong&gt;200GB&lt;/strong&gt; of events, and 90% of this data becomes infrequently accessed over time (so sealed).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Frequent access&lt;/strong&gt;: 20GB × $0.023 = &lt;strong&gt;$0,46/month&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Infrequent access&lt;/strong&gt;: 180GB × $0.0125 = &lt;strong&gt;$2,25/month&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Total storage costs&lt;/strong&gt;: &lt;strong&gt;$2.71/month&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This represents a &lt;strong&gt;41% reduction&lt;/strong&gt; in storage costs compared to keeping everything in S3 Standard.&lt;/p&gt;
&lt;h4 id=&quot;3-cold-storage-archived-data-in-s3-glacier-or-deep-archive&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-cold-storage-archived-data-in-s3-glacier-or-deep-archive&quot; aria-label=&quot;3 cold storage archived data in s3 glacier or deep archive permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;3. Cold Storage: Archived Data in S3 Glacier or Deep Archive&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Archived event streams that we need to retain for auditing or compliance but rarely access can be moved to &lt;strong&gt;S3 Glacier&lt;/strong&gt; or &lt;strong&gt;S3 Glacier Deep Archive&lt;/strong&gt;. These tiers offer the lowest storage costs but longer retrieval times, which is acceptable for long-term archival data.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cost&lt;/strong&gt;: $0.004 per GB/month for Glacier, and $0.00099 per GB/month for Glacier Deep Archive.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access&lt;/strong&gt;: Long retrieval times (minutes to hours), suitable for rarely accessed data.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Assume you archived the last year’s data of &lt;strong&gt;100GB&lt;/strong&gt; of data in &lt;strong&gt;S3 Glacier&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage cost&lt;/strong&gt;: 200GB × $0.004 = &lt;strong&gt;$0.8/month&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you move some of this data to &lt;strong&gt;S3 Glacier Deep Archive&lt;/strong&gt; for even lower costs, you can further reduce the expense:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage cost for 200GB in Deep Archive&lt;/strong&gt;: 200GB × $0.00099 = &lt;strong&gt;$0.198/month&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This dramatically reduces storage costs for long-term data that rarely needs access.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;delete-requests-vs-moving-data-to-lower-tiers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#delete-requests-vs-moving-data-to-lower-tiers&quot; aria-label=&quot;delete requests vs moving data to lower tiers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;DELETE Requests vs. Moving Data to Lower Tiers&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Why don’t we just delete data? We could do it if we don’t need them for legal reasons, but… Here is the warning: we may fall into unexpected costs. We already included the LIST request in our calculations. It can be easily overlooked as a detail, but it can cost a lot. The same goes for DELETE operations.&lt;/p&gt;
&lt;p&gt;S3 charges &lt;strong&gt;$0,005 per thousand DELETE requests&lt;/strong&gt;, which is the same cost as appending a new event. Effectively, that doubles the append cost. However, frequent deletions can add up if you regularly compact and clean old chunks. Even if we do batch operations, it’ll charge us per chunk.&lt;/p&gt;
&lt;p&gt;What if we just let it sink into cheaper storage (S3 Glacier or S3 Intelligent-Tiering)? We’ll also need to pay for that. The &lt;strong&gt;transition&lt;/strong&gt; costs are —&lt;strong&gt;$0.05 per 1,000 objects&lt;/strong&gt; when moving to Glacier. So, it’s ten times higher than deletion.&lt;/p&gt;
&lt;p&gt;As always, pick your poison.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;handling-traffic-spikes-and-partitioning-strategy&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#handling-traffic-spikes-and-partitioning-strategy&quot; aria-label=&quot;handling traffic spikes and partitioning strategy permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Handling Traffic Spikes and Partitioning Strategy&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;S3’s pricing model works in your favour during spikes. It’s request-based and storage-based, so you’re not hit with additional costs just because traffic increases.&lt;/strong&gt; However, the real risk during traffic spikes is S3 throttling—where too many requests in a short time can lead to &lt;em&gt;503 Slow Down&lt;/em&gt; errors. This won’t increase your costs directly, but it’s an operational challenge you need to manage.&lt;/p&gt;
&lt;p&gt;Previously, we discussed the importance of structuring object keys to manage concurrency and versioning. The same approach can help you handle traffic surges without hitting throttling limits.&lt;/p&gt;
&lt;p&gt;Here are some &lt;strong&gt;partitioning strategies&lt;/strong&gt; to distribute your load more effectively:&lt;/p&gt;
&lt;h4 id=&quot;1-partition-by-tenant&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-partition-by-tenant&quot; aria-label=&quot;1 partition by tenant permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;1. Partition by Tenant&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;If you run a &lt;strong&gt;multi-tenant&lt;/strong&gt; system, separating each tenant’s data reduces the chances of one tenant’s spike affecting others. For example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;s3://tenantA-bucket/orders/ORD-293e/001.parquet
s3://tenantB-bucket/orders/ORD-293e/002.parquet
s3://tenantB-bucket/orders/ORD-f354/001.parquet&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By isolating tenants in separate partitions, you ensure better performance and avoid &lt;strong&gt;hot partitions&lt;/strong&gt; during surges.&lt;/p&gt;
&lt;h4 id=&quot;2-partition-by-module-or-feature&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-partition-by-module-or-feature&quot; aria-label=&quot;2 partition by module or feature permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;2. Partition by Module or Feature&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;For systems that manage &lt;strong&gt;multiple business areas&lt;/strong&gt; (e.g., orders, payments, and shipments), partitioning by module helps spread the load. For example:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;s3://policies-bucket/tenantA/POL-mmc6/001.parquet
s3://claims-bucket/tenantB/CLA-9jnvj/001.parquet
s3://payments-bucket/tenantA/PAY-12f3/001.parquet
s3://payments-bucket/tenantB/PAY-fk98/001.parquet
s3://payments-bucket/tenantB/PAY-fk98/002.parquet&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This approach ensures that spikes in one module (say, orders during Black Friday) don’t overwhelm the entire system.&lt;/p&gt;
&lt;h4 id=&quot;3-time-based-partitioning&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-time-based-partitioning&quot; aria-label=&quot;3 time based partitioning permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;3. Time-Based Partitioning&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;For systems with heavy &lt;strong&gt;batch processing&lt;/strong&gt; or time-sensitive data, partitioning by time (year/month/day) is an effective way to reduce hot spots and organize data logically:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;s3://2023-bucket/tenantA/POL-mmc6/001.parquet
s3://2024-bucket/tenantA/POL-mmc6/002.parquet
s3://2024-bucket/claims/tenantB/CLA-9jnvj/001.parquet
s3://2025-bucket/budget/tenantB/BUD-gdh83/002.parquet&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id=&quot;cutting-the-number-of-requests&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cutting-the-number-of-requests&quot; aria-label=&quot;cutting the number of requests permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cutting the number of requests&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;We cannot easily cut the number of GET and PUT requests without using a cache.&lt;/strong&gt; But in our case, that won’t help much as the typical case for transactional operation is that you get the state, run business logic and update it. Each update invalidates the previous state. Even though in Event Sourcing you don’t update the state but append the event, then in our case, we’ll be appending to the new chunk, invalidating the previous one. If we had much more reads than writes, we could benefit from the connection of AWS Cloudfront caching to S3, but it costs 0.0075$ per 1000 HTTP requests, so even more than regular S3. It’d work if we had a huge payload, but our chunks will be small.&lt;/p&gt;
&lt;p&gt;If we have a stateful backend or frontend, we can cache the information about the latest chunk (so the record plus its version). For example, when a new event is appended, cache the name of the active chunk and the version, setting a time-to-live (TTL) for the cached value. If we frequently update the same record, the LIST requests could be lowered, as we could go directly to the active chunk. Even if our cache is invalid, the conditional writes mechanism won’t allow us to modify the state with a stale version.&lt;/p&gt;
&lt;p&gt;We could also use DynamoDb to store the information about the active chunk. If data is small, then an additional request to DynamoDB can be cheaper than a LIST request to S3.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;future-cost-improvements-if-match-and-beyond&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#future-cost-improvements-if-match-and-beyond&quot; aria-label=&quot;future cost improvements if match and beyond permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Future Cost Improvements: IF-MATCH and Beyond&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;We’re currently working within the limitations of &lt;strong&gt;If-None-Match&lt;/strong&gt; conditional writes, which work well for creating new versions of objects but complicate updating existing ones. If AWS introduces &lt;strong&gt;IF-MATCH&lt;/strong&gt; support (which lets us update an object only if it matches a specific version), this could simplify the architecture dramatically.&lt;/p&gt;
&lt;p&gt;Here’s how:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lower LIST costs:&lt;/strong&gt; We wouldn’t need to append a new file each time. We could just update the same file representing the stream. We could benefit from &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html&quot;&gt;built-in S3 versioning capabilities&lt;/a&gt;. Our ID wouldn’t be built from stream ID and autoincremented version, but just stream ID, making it fully predictable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lower DELETE costs&lt;/strong&gt;: No more need for frequent cleanup of old event chunks. We could just decommission older versions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Easier caching&lt;/strong&gt;: Fewer invalidations since you’d have more control over updates.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This would reduce request costs significantly, allowing for a cleaner and more efficient event store design.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;While we don’t have this feature yet, it’s worth watching, as it could lead to substantial cost savings.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;load-testing-ftw&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#load-testing-ftw&quot; aria-label=&quot;load testing ftw permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Load Testing FTW&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Even with all the math, you’ll never know the true costs of running an S3-based event store until you &lt;strong&gt;test it in the real world&lt;/strong&gt;. &lt;strong&gt;Load testing&lt;/strong&gt; will give you the hard data you need to make better decisions.&lt;/p&gt;
&lt;p&gt;Set up tests to simulate &lt;strong&gt;peak traffic&lt;/strong&gt;, &lt;strong&gt;high-write scenarios&lt;/strong&gt;, and &lt;strong&gt;spikes in reads&lt;/strong&gt;. This will help you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Identify &lt;strong&gt;hidden costs&lt;/strong&gt; that aren’t obvious in theoretical calculations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fine-tune your architecture for both performance and cost.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Avoid &lt;strong&gt;unexpected bills&lt;/strong&gt; by catching inefficiencies early.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, a load test might reveal that your caching strategy is insufficient, causing unnecessary GET requests that drive up your costs. Or, it might show that your DELETE operations are happening more frequently than expected, increasing the operational overhead.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up-cost-management-as-an-ongoing-process&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#wrapping-up-cost-management-as-an-ongoing-process&quot; aria-label=&quot;wrapping up cost management as an ongoing process permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Wrapping Up: Cost Management as an Ongoing Process&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Estimating cloud costs is inherently challenging.&lt;/strong&gt; As we’ve seen in this article, &lt;strong&gt;even a seemingly straightforward system like an event store on S3 involves multiple cost drivers—from storage and request operations to data access patterns and lifecycle management.&lt;/strong&gt; This example illustrates a broader point: cloud cost management requires a deep understanding of how architectural decisions impact expenses.&lt;/p&gt;
&lt;p&gt;Here are some guiding principles to help you approach cost management strategically:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Request Costs vs. Storage Costs&lt;/strong&gt;: In our example, we found that request costs (PUT, GET, LIST, DELETE) can often outpace storage costs, especially as traffic increases. This holds true for many cloud architectures. Understanding when and where your system generates the most requests is crucial to keeping costs under control. Design systems that optimize for storage efficiency and operational cost rather than focusing on one aspect in isolation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Optimize Storage Based on Access Patterns&lt;/strong&gt;: The investigation into S3 tiers (Standard, Intelligent-Tiering, Glacier) shows how access frequency affects cost. This concept of “hot,” “warm,” and “cold” data can be applied to many systems beyond event stores. Choose your storage tiers based on actual usage patterns to avoid paying for unnecessary performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lifecycle Management and Data Cleanup&lt;/strong&gt;: Managing data deletions, compaction, and tiering—like we discussed with event stream compaction—are crucial to keeping long-term costs down. Automating transitions between storage classes or deleting obsolete data can dramatically reduce storage overhead, but these decisions need to be weighed against the potential cost of requests and transitions. Every architectural choice, like managing DELETE requests or deciding when to compact data, affects the bottom line.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test for Real-World Conditions&lt;/strong&gt;: Load testing is a key practice for understanding true cost drivers in cloud environments. As our exploration shows, the theoretical calculations are only part of the picture—testing reveals hidden costs, whether they stem from unexpected GET requests, cache inefficiencies, or the need to scale up for traffic spikes. Simulating realistic traffic is essential to accurately predicting your system’s long-term costs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Continuous Cost Monitoring&lt;/strong&gt;: Cloud platforms evolve, as do workloads and traffic patterns. As our investigation shows, even small architectural changes (e.g., switching from JSON to Parquet) can lead to significant cost savings. Regularly monitoring and revisiting your cloud strategy is key to staying efficient, whether by adopting new services or optimizing existing ones.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;The goal of this article was not just to analyze the cost of a specific event store design on S3 but to demonstrate how to approach cloud cost management more generally.&lt;/strong&gt; Each architectural decision, from choosing data formats to managing storage tiers and optimizing requests, directly impacts your cloud expenses.&lt;/p&gt;
&lt;p&gt;By applying the same thought process—identifying key cost drivers, modeling different scenarios, testing under load, and continuously refining your strategy—you can tackle cost optimization across any cloud-based architecture. Cloud cost management is not a one-time effort but an ongoing adaptation, testing, and refinement process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In the end, understanding the real cost of cloud services requires a holistic view, one that balances performance, scalability, and efficiency.&lt;/strong&gt; This ensures that your infrastructure not only meets your operational needs but does so cost-effectively.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can also check my small spreadsheet with calculations: &lt;a href=&quot;https://docs.google.com/spreadsheets/d/1GeMM3C8EYpSQJMFjcXt-xa4xp61LQ3E1dHEzWraErZ0/edit?usp=sharing&quot;&gt;https://docs.google.com/spreadsheets/d/1GeMM3C8EYpSQJMFjcXt-xa4xp61LQ3E1dHEzWraErZ0/edit?usp=sharing&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As I said in the beginning. Feel free to share your thoughts and ideas and discuss them under the article or in our community Discord channel!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m happy to be proven wrong!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using S3 but not the way you expected. S3 as strongly consistent event store]]></title><description><![CDATA[Welcome to the new week! Hype is strong in our industry, but the most powerful news usually comes surprisingly silent. That’s what happened…]]></description><link>https://event-driven.io/en/using-s3-but-not-the-way-you-expected/</link><guid isPermaLink="false">https://event-driven.io/en/using-s3-but-not-the-way-you-expected/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hype is strong in our industry, but the most powerful news usually comes surprisingly silent. That’s what happened with the latest AWS S3 update.&lt;/strong&gt; Such a humble update on introducing new features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/about-aws/whats-new/2024/08/amazon-s3-conditional-writes&quot;&gt;Amazon S3 now supports conditional writes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Okay, it didn’t go entirely silent. Gunnar Morling did a great write-up showing how you can now conduct a &lt;a href=&quot;https://www.morling.dev/blog/leader-election-with-s3-conditional-writes/&quot;&gt;Leader Election With S3 Conditional Writes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That already shows how powerful this can be, and I want to show you today another unexpected use case of that: so &lt;strong&gt;building a strongly consistent event store using conditional writes for optimistic concurrency!&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Before I move on, I promised you that once per month, I’ll write an open article for all subscribers, and this article is like that. &lt;strong&gt;There is no paywall today!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Still, check articles and webinars from last month; I think they’re worth subscribing to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;#189 - Mastering Database Connection Pooling&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;#190 - Queuing, Backpressure, Single Writer and other useful patterns for managing concurrency&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/architecture-weekly-191-is-it-production/&quot;&gt;#191 - Is It Production Ready?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/talk-is-cheap-show-me-the-numbers/&quot;&gt;#192 - Talk is cheap, show me the numbers! Benchmarking and beyond!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/webinar-21-michael-drogalis-building/&quot;&gt;Webinar #21 - Michael Drogalis: Building the product on your own terms&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;s3-as-more-than-just-storage&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#s3-as-more-than-just-storage&quot; aria-label=&quot;s3 as more than just storage permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;S3 as More Than Just Storage&lt;/h2&gt;
&lt;p&gt;Let’s take a moment to appreciate just how far Amazon S3 has come since its debut. Initially, S3 was the place where you dumped your backups, logs, and static files—reliable and cheap but not exactly groundbreaking. Fast-forward to today, S3 has evolved into something far more versatile. It’s not just a storage service anymore; it’s a foundational piece of infrastructure enabling a new wave of distributed applications and services (e.g., &lt;a href=&quot;https://turso.tech/&quot;&gt;Turso&lt;/a&gt;, &lt;a href=&quot;https://www.warpstream.com/&quot;&gt;WarpStream&lt;/a&gt;, etc.).&lt;/p&gt;
&lt;p&gt;Over the years, developers have realised that S3 can be used for much more than just file storage. It’s now being used to store database snapshots, as the backbone for event-driven architectures, and as a core component in serverless computing models. S3’s flexibility, scalability, and durability have made it a go-to service for architects looking to build robust, cost-effective systems.&lt;/p&gt;
&lt;p&gt;Read also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.allthingsdistributed.com/2023/07/building-and-operating-a-pretty-big-storage-system.html&quot;&gt;Werner Vogels - Building and operating a pretty big storage system called S3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, as we push S3 into these new roles, we encounter challenges that weren’t part of its original design brief.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One of the biggest challenges is managing concurrency—ensuring that multiple processes can read and write data without stepping on each other’s toes. This is where S3’s new conditional writes feature comes in.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;why-conditional-writes-matter&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-conditional-writes-matter&quot; aria-label=&quot;why conditional writes matter permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Conditional Writes Matter&lt;/h2&gt;
&lt;p&gt;Conditional writes are a game-changer for anyone looking to use S3 in more complex scenarios. At its core, a conditional write is a way to ensure that an update only happens if the data hasn’t changed since you last read it. This might sound simple, but it’s incredibly powerful when you’re dealing with distributed systems, where multiple processes might try to update the same data simultaneously.&lt;/p&gt;
&lt;p&gt;S3 didn’t have this capability for a long time, which meant you had to rely on external systems—like DynamoDB or even running your own databases—to handle this kind of concurrency control. This added complexity, cost, and operational overhead. With conditional writes, S3 can now manage this itself, making it a more attractive option for a broader range of applications.&lt;/p&gt;
&lt;p&gt;So, what does this mean in practice? S3 can now serve as the foundation for a new generation of tools and services. Take Turso, for example, a service that uses a custom fork of SQLite to handle multi-region, low-latency data storage. Or Warp Stream, an event streaming service that benefits from the ability to store, process, and replay events in a distributed manner.&lt;/p&gt;
&lt;p&gt;These tools are pushing the boundaries of what’s possible with cloud storage, and S3’s conditional writes are a key enabler. By allowing developers to implement optimistic concurrency directly in S3, these tools can offer strong consistency guarantees without the need for complex, external coordination mechanisms.&lt;/p&gt;
&lt;h2 id=&quot;optimistic-concurrency-and-conditional-writes-why-it-matters&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#optimistic-concurrency-and-conditional-writes-why-it-matters&quot; aria-label=&quot;optimistic concurrency and conditional writes why it matters permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Optimistic Concurrency and Conditional Writes: Why It Matters&lt;/h2&gt;
&lt;p&gt;Every system faces the challenge of concurrency. Imagine you’re managing an order system for an e-commerce platform. Orders come in from multiple sources, and various microservices interact with the same data. For example, one service processes payments while another updates the inventory. Both might attempt to update the same order simultaneously. Without proper concurrency control, race conditions and data corruption can occur.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimistic concurrency is a strategy where we (optimistically) assume that a scenario will be rare when more than one person tries to edit the same resource (a record).&lt;/strong&gt; This assumption is correct for most system types. Having that, we can compare the expected record version (or timestamp) with the actual version in the database. If they match, then we perform the update. If the data has changed, the transaction is aborted, and the process can handle the conflict—often by retrying or alerting the user.&lt;/p&gt;
&lt;p&gt;Read more details in my other articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/optimistic_concurrency_for_pessimistic_times/&quot;&gt;Optimistic concurrency for pessimistic times&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/how_to_use_etag_header_for_optimistic_concurrency/&quot;&gt;How to use ETag header for optimistic concurrency&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Optimistic concurrency and atomic writes ensure that our decisions are based on the latest state. This capability is also one of the most &lt;a href=&quot;/en/event_streaming_is_not_event_sourcing/&quot;&gt;important differences between databases and messaging tooling&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do you implement something similar with Amazon S3, primarily designed for storing files, not managing transactions?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before S3 introduced conditional writes, implementing optimistic concurrency was tricky. S3 didn’t natively support the ability to check whether an object had been modified before writing an update, leaving developers to cobble together various workarounds. These workarounds often involved additional infrastructure, such as using DynamoDB to track versions or implementing custom locking mechanisms, adding complexity and cost.&lt;/p&gt;
&lt;h3 id=&quot;the-first-attempt-using-s3-without-version-control&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-first-attempt-using-s3-without-version-control&quot; aria-label=&quot;the first attempt using s3 without version control permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The First Attempt: Using S3 Without Version Control&lt;/h3&gt;
&lt;p&gt;Let’s start with the most common approach: storing each order as an object in S3 without any version control. For instance, you might store an order with the key &lt;em&gt;order123.json&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;When a process updates the order, it overwrites the existing &lt;em&gt;order123.json&lt;/em&gt; file with the new data (S3 cannot patch or append data to a file. Files in S3 are immutable).&lt;/p&gt;
&lt;p&gt;If two processes try to update the order at the same time, the last one to finish will overwrite the changes made by the other, leading to potential data loss.&lt;/p&gt;
&lt;p&gt;This approach clearly lacks the necessary controls to manage concurrency, as it doesn’t prevent overwriting data.&lt;/p&gt;
&lt;h3 id=&quot;adding-version-numbers-to-object-names&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#adding-version-numbers-to-object-names&quot; aria-label=&quot;adding version numbers to object names permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Adding Version Numbers to Object Names&lt;/h3&gt;
&lt;p&gt;To address this, you might consider adding a version number to the object names, such as &lt;em&gt;order123:001.json&lt;/em&gt;, &lt;em&gt;order123:002.json&lt;/em&gt;, and so on. This way, each update creates a new version of the order.&lt;/p&gt;
&lt;p&gt;When a process updates an order, it writes a new object with the following version number, like &lt;em&gt;order123:002.json&lt;/em&gt;. While this prevents overwriting the previous version, there’s still no mechanism to ensure that the version being updated is the latest one. Two processes could still generate the same new version number, &lt;em&gt;order123:002.json&lt;/em&gt;, leading to a race condition where one update overwrites the other.&lt;/p&gt;
&lt;h3 id=&quot;why-not-use-s3s-native-versioning&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-not-use-s3s-native-versioning&quot; aria-label=&quot;why not use s3s native versioning permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Not Use S3’s Native Versioning?&lt;/h3&gt;
&lt;p&gt;S3 has a built-in versioning feature, where you can enable versioning on a bucket, and S3 will automatically keep track of all versions of an object. However, this doesn’t solve the problem of optimistic concurrency because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Conditional Checks&lt;/strong&gt;: S3’s native versioning doesn’t allow you to check which version of an object you’re updating conditionally. It simply stores every update as a new version, regardless of whether the object has been modified since you last read it. Essentially, the next version of a file is a different file, just sharing the file name, differentiating with the version number.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lack of Granular Control&lt;/strong&gt;: You can’t enforce a rule that says, “Only update this object if it’s still on version 001.” This lack of control means you can still face race conditions and data integrity issues.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-solution-using-conditional-writes-with-custom-versioning&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-solution-using-conditional-writes-with-custom-versioning&quot; aria-label=&quot;the solution using conditional writes with custom versioning permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Solution: Using Conditional Writes with Custom Versioning&lt;/h2&gt;
&lt;p&gt;Given these limitations, we can use S3’s conditional writes feature to implement a custom versioning strategy that supports optimistic concurrency. Here are the ingredients to make it work:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Naming Strategy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We’ll use a structured naming convention for our objects, including the version number as part of the object key. For example, an order might be stored as &lt;em&gt;ecommerce/tenantA/orders/12345/001.json&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The `001` in the key represents the order’s version. Every time the order is updated, a new object with an incremented version number (e.g., &lt;em&gt;12345:002.json&lt;/em&gt;) is created.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Using `If-None-Match` for Conditional Writes:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We use the &lt;em&gt;&lt;strong&gt;If-None-Match=”*”&lt;/strong&gt;&lt;/em&gt; header when updating an order. This tells S3 to only proceed with the write operation if an object with the specified key (e.g. &lt;em&gt;order123:002.json&lt;/em&gt;) does not exist.&lt;/p&gt;
&lt;p&gt;This prevents two processes from creating the same version number, ensuring each update creates a new, unique version.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Handling Conflicts:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If the &lt;em&gt;&lt;strong&gt;If-None-Match=”*”&lt;/strong&gt;&lt;/em&gt; condition fails (resulting in a &lt;em&gt;`412 Precondition Failed`&lt;/em&gt; response), it means another process has already created that version. The process can then reread the latest version, resolve the conflict, and retry the operation with the next version number.&lt;/p&gt;
&lt;p&gt;For instance:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 631px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/069a4624434dbeb1d3b483f3658f6b00/e3151/2024-09-02-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 128%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAAsTAAALEwEAmpwYAAADBklEQVR42pVVWXacQAycwyQeaKDZmh08m9fYSZ5f7n8WpUoEzzBgJ/no1wvqaklVEhvPlLI2/KASm9QSxaXESSWRdbr2TCEf3eHYrB1u/UIBgygDSC6ubMXGmZgwwVmj3yeb67FZAzMhPeskjCrsc8w1jB3OSz23Sa/AN14hX7c5ZqeDdxeACmRbSbNb6W+fpW7vZX98lWH3TcrqpGC8mOU7OZy+63nTPcD2STK3OwPSiK/apNM1z8r6pIbd8CgHgPKiH9R/Hq8A9izH0w9p+xEwL/YXHgIkilvNw7XXDIfndXOCZwy3lDTv4PFegZekBGS11HAuEzxjPeRZIknqQFIjMeatZzU9U0QKyM2U1DHR5wRfy4iemdBBRrXO476ee0gScrfX0faP8GCABwd40M9AubawTUFG3d5JAYKYz+uHN7xIFveHF3l4etP17vD6zualMUngd9qd7n8qaQtAhmLCRpKsl/uHN5WGCauFIfdkmaCMZICkXHFYAk7GnGOEe1kF1zlkNHHaacjUHJ3Y+p+UHjX4USmSsDitkd8eHkI+DuLPlzL7J0DPn5oFQRuA7iDiAaDDesh/BQxKZZflxrSQDFZFjhySvP8GpHcVQIryCDVQBS9ajkV1VInNhD3m6NwYznmbg35BV5kAKRmCpvmtdpuZh5QI2xIbZ1EddO0HaGHRvAICVESgbawE8F5JCm2jEpsBhhYy8TxUQYyubFFWifjGIHz33kyp04ClFrKWC8gFOc3oSCy8f9kgNpGFRyZAwh2MHBKN0G2MB8ZGOwH6JgNgCmZr2FI+3djBwyvABI2UiWU+mGTOrOtJ5JMOuU+yQVscRc2ZwmYUs5AzXCZjrFF24MenX9qFJ0AyzO7Dx2h3B0JGUkZ7EjljmcaUCzXGl3mRg4bj65UyHtoO0rnTWldNwhGOMS0rtTz1wRsvhxQcwulQ/K3++YIoxY8qA2Hw2OQz+9VKWf4OayXBhBE8LwBcSlWBcc/Ht1Q9X+vq7yGvDeoyiAr9fXqGI9cuTR1+dIfjN2wVMtmcCPilAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/069a4624434dbeb1d3b483f3658f6b00/507e6/2024-09-02-cover.webp 200w,
/static/069a4624434dbeb1d3b483f3658f6b00/28a80/2024-09-02-cover.webp 400w,
/static/069a4624434dbeb1d3b483f3658f6b00/db638/2024-09-02-cover.webp 631w&quot;
              sizes=&quot;(max-width: 631px) 100vw, 631px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/069a4624434dbeb1d3b483f3658f6b00/36ca5/2024-09-02-cover.png 200w,
/static/069a4624434dbeb1d3b483f3658f6b00/a3397/2024-09-02-cover.png 400w,
/static/069a4624434dbeb1d3b483f3658f6b00/e3151/2024-09-02-cover.png 631w&quot;
            sizes=&quot;(max-width: 631px) 100vw, 631px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/069a4624434dbeb1d3b483f3658f6b00/e3151/2024-09-02-cover.png&quot;
            alt=&quot;2024 09 02 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-key-to-consistency-is-a-proper-key-strategy&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-key-to-consistency-is-a-proper-key-strategy&quot; aria-label=&quot;the key to consistency is a proper key strategy permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The key to consistency is a proper key strategy&lt;/h3&gt;
&lt;p&gt;Yeah, it’s a lame joke that I’m famous for, but that’s true. When working with S3 to implement optimistic concurrency, we need a structured and predictable file naming convention to manage versions effectively. The naming strategy we’ve chosen is:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;{streamPrefix}/{streamtType}/{streamId}/{streamVersion}&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We’re building an event store here. Event stores are (logically) key-value databases; the key is a record, and the value is an ordered list of events. In relational databases, records are called rows; in document databases: documents; in Event Sourcing, they’re called streams.&lt;/p&gt;
&lt;p&gt;I wrote about it longer in my &lt;a href=&quot;/en/event_stores_are_key_value_stores/&quot;&gt;my article&lt;/a&gt;. I also explained how to build your own on relational database. In one hour!&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/gaoZdtQSOTo?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Here, we’re building an event store in one article! Huh!&lt;/p&gt;
&lt;p&gt;Let’s break this down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stream prefix:&lt;/strong&gt; This is an optional part of the path that can be used to organise your streams by system, module, tenant, or any other logical grouping. For example, if you’re working with a multi-tenant system, this prefix could be the tenant ID or name, allowing you to partition your data easily, e.g. by the tenant.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stream type:&lt;/strong&gt; This denotes the category of stream, such as orders, payments, or shipments (think process or entity type traditionally). It helps categorise the different types of data you’re storing, which is crucial when you have a complex system with multiple data streams.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stream id:&lt;/strong&gt; This is the unique identifier for the specific stream. For example, it could be the order ID in an order processing system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stream Version:&lt;/strong&gt; This is crucial to managing concurrency. It’s an auto-incremented version number that represents the current state of the stream. Each time an update is made, this number increases by one, ensuring that each stream version is uniquely identifiable.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Stream Version should be gapless and not tied to streams or operation events count.&lt;/strong&gt; This is important because if two processes tried to append events simultaneously, and if the version was tied to the number of events, they could generate the same version number, leading to data integrity issues. Using a gapless, auto-incremented version number ensures that each update has a unique version.&lt;/p&gt;
&lt;p&gt;For example, consider two processes trying to append events to the same stream:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The stream initial stream version is&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;008&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;.&lt;/strong&gt; Process A tries to append 2 events, and Process B tries to append 1 event simultaneously.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Process&lt;/strong&gt; &lt;strong&gt;A&lt;/strong&gt; would name the next chunk with &lt;strong&gt;version&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;010&lt;/strong&gt;&lt;/em&gt; (&lt;em&gt;008&lt;/em&gt;+2),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Process B&lt;/strong&gt; would name the next chunk with &lt;strong&gt;version&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;009&lt;/strong&gt;&lt;/em&gt; (&lt;em&gt;008&lt;/em&gt;+1)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;This would lead to both updates being accepted,&lt;/strong&gt; as there were no files named like that and even with conditional writes, we wouldn’t be able to detect it (as they check if the file exists or not). That could mean an inconsistent state.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;An autoincremented, gapless version based on the previous file name would not allow that, as both would try to append the file name with version &lt;em&gt;009&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Essentially, the stream version is a &lt;a href=&quot;https://martinfowler.com/articles/patterns-of-distributed-systems/lamport-clock.html&quot;&gt;logical timestamp&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;finding-the-latest-chunk&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#finding-the-latest-chunk&quot; aria-label=&quot;finding the latest chunk permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Finding the latest chunk&lt;/h3&gt;
&lt;p&gt;Such a key structure also allows for finding the latest chunk. We can use the &lt;a href=&quot;https://docs.aws.amazon.com/cli/v1/userguide/cli-usage-filter.html#cli-usage-filter-server-side&quot;&gt;list objects API together with filtering&lt;/a&gt; and determine the latest chunk by checking the LastModified timestamp provided by S3. For instance, using the AWS CLI:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;aws s3api list-objects-v2 --bucket your-bucket-name --prefix &lt;span class=&quot;token string&quot;&gt;&apos;ecommerce/tenantA/orders/12345/&apos;&lt;/span&gt; --query &lt;span class=&quot;token string&quot;&gt;&apos;Contents | sort_by(@, &amp;amp;LastModified)[-1].Key&apos;&lt;/span&gt; --output text&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This command lists all objects with the stream name prefix &lt;em&gt;ecommerce/tenantA/orders/12345/&lt;/em&gt; and sorts them by the built-in &lt;em&gt;LastModified&lt;/em&gt; timestamp This will work as we’re just doing append-only operations.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;[-1]&lt;/em&gt; index retrieves the last item in the sorted list, corresponding to the most recently modified chunk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The filtering happens on the server side, but sorting on the client side poses a challenge, as if you have more than 1000 chunks, you’ll need to paginate the result.&lt;/strong&gt; If that happens, you can also keep the metadata file containing information about the last modified chank and update it once per few requests. Then you can add filtering param &lt;em&gt;—start-after&lt;/em&gt; that skips over files that come before the specified key, potentially speeding up the listing process.&lt;/p&gt;
&lt;p&gt;You can also reconcile old chunks, but we’ll discuss that later.&lt;/p&gt;
&lt;h3 id=&quot;example-chunk-structure&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#example-chunk-structure&quot; aria-label=&quot;example chunk structure permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Example chunk structure&lt;/h3&gt;
&lt;p&gt;The JSON file stored in S3 will contain metadata about the stream and the events. Here’s an example structure:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/922fe61fbeaa4202c521801601460e6c/ba4a5/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 180.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAkCAYAAACJ8xqgAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEHUlEQVR42p1Wx5LbRhDlx4hEmBmEAWaQGVbyeiVdfPDN5S9wKMlan+zP1sEqlKv9ugGmMkmJe+gCmBr9Ug8XoSnsUhV/rbT7I1Dl80sq5N8a93cUlfkiSvzzz/2WfvrRj/EPNUVJRXF6X6HHaGyHe/9xEWj3KctrMlnxJUyLf0N1rOCkQl1erUCXX1RaU6jKD4tA2eekHCg0bsQbhC/wEykyJcWJw72T+2D+7FLhgaM0jAtMCA74ReyG0QzfUbJ9oqR7QyprKK8bqndrSl3DcO5sWPaj6dFw/b2UqbaY0JNbD+SHnjLf4sdumlRNE++nvtCwIjVPmG6eiBtLtTuyrqWm6UhhQpU5UrkXKuLUkc79rQmHUSbr31C6e0va9pQUFfntQHnTksOUTIFbT9ei66jse/Dr/98wTqYJ0/UjKUA14E8DbhAXU6ljreKpwvn1xQm5YQSVdd5QwEQbKIsvRHxNyjPFGfoe6k1RYJ8xApcGCieYNBkwLR5Qti15wLQMEcWQGS5DvdGwkjejrJZmunkQ68RwvwOPWV7Rcg+fIUdHuLcbgjcWxaChKTqZcMWkowRu5s/qakPOIz5AUixFiGHK5t4+SXO7eaS8GqioAL/vpho6snUrHN+eEB/GgK3bB9L1VoRx1lPfIDG+FmX3KjP026LEdtS2pcQh16zotEUkHa/A2TKyB95Y9dCU38LhrDLgpg/vSKN5VrQwOThL3YG3gy9nj+JhY3yRw9jKZGKbDVJT7yj1HRVsHSRG7IMYsg+NrXCt5D4pK3j40oTGyZbRaMQc6vY1ab8h5paFSD1HroVIU/QsIpkhhn6LxWIb5vZ8QgObxBI5O0OyJMqDr1V8jJ5c2YvR9PpVmI/S4zR6aDSm8N7UZMrqxFc5c3VcWaLwmTAXfAhYo4JlIp4SjWMoLmZmvspKNo9BpQ4+Les7sgwzJ7y5sRcVGpcwsHBVTRuc77nuyDLUbXZQeCvRy3i7gNfVnOEl8wZPfmXCkyxzs+41Gb8mXfQUQnV+EG9otkoCuKaoD+LczDILwj9OxdjvZdp0B4OXvVilfljT8LSTFbb34yTUlQlj+JBtw6IoTBhjQuV6eY+ny+uJS26U+UaMfaXhSZYBmRVmQXhyNR8FzN8qnNe/PsZOIOsrE/Lq54RMUN9KltOiEauIZXwt5/PpGcONv5Llej6bH3GMYnPXa4HKBz5z54ZBDn6OHEeR723bnkfvwCHzxzAhglS1AfReIDJ3ojKmZKUzXHnyBJU3zfly4OhNWW6nXcdZnouzzDztz5EgLo/H61zL8AyyfebI6awWlU8Pn2+tM5VXyn5IoCrI/ozm/wDe3YWGn2VL6eJ3+cOZlfBcUslTXlL82wR/XQD742KpfIrOn/CkX3H97SWFNfYLtPgzMLX5D5q0KBvEjhq7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/922fe61fbeaa4202c521801601460e6c/507e6/image-2.webp 200w,
/static/922fe61fbeaa4202c521801601460e6c/28a80/image-2.webp 400w,
/static/922fe61fbeaa4202c521801601460e6c/8d2ea/image-2.webp 800w,
/static/922fe61fbeaa4202c521801601460e6c/f1046/image-2.webp 866w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/922fe61fbeaa4202c521801601460e6c/36ca5/image-2.png 200w,
/static/922fe61fbeaa4202c521801601460e6c/a3397/image-2.png 400w,
/static/922fe61fbeaa4202c521801601460e6c/a331c/image-2.png 800w,
/static/922fe61fbeaa4202c521801601460e6c/ba4a5/image-2.png 866w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/922fe61fbeaa4202c521801601460e6c/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-write-ahead-pattern-a-familiar-approach&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-write-ahead-pattern-a-familiar-approach&quot; aria-label=&quot;the write ahead pattern a familiar approach permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Write-Ahead Pattern: A Familiar Approach&lt;/h2&gt;
&lt;p&gt;Our outlined approach is strikingly similar to the write-ahead pattern used in databases. In traditional databases, a write-ahead log (WAL) ensures that any changes to the data are first recorded in a log before they’re actually applied to the database. This ensures that even in the event of a crash or failure, the database can recover by replaying the log and returning the system to a consistent state.&lt;/p&gt;
&lt;p&gt;In our case, each stream operates similarly. We append new events to a chunk file in S3, with each version of the stream corresponding to a new update. This ensures that our event stream is always consistent, and if something goes wrong, we can trace back through the versions to recover the correct state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;However, unlike traditional databases that might use a single log for the entire system, our approach involves multiple logs—one for each stream.&lt;/strong&gt; This logical grouping allows us to efficiently read and write data per stream, making it scalable across different use cases, like processing orders, payments, or shipments. Read more &lt;a href=&quot;/en/relational_databases_are_event_stores/&quot;&gt;in What if I told you that Relational Databases are in fact Event Stores?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;While this approach ensures consistency and helps manage concurrency, it comes with &lt;strong&gt;cost considerations that are particularly relevant when using S3 for event sourcing.&lt;/strong&gt; In a typical event sourcing scenario, every time you want to build the state of an entity (like an order), you need to read all the events in the stream from the beginning to the current state. This means every operation could potentially involve reading all the chunks associated with a stream.&lt;/p&gt;
&lt;p&gt;And here’s where the S3 costs start to add up: S3 charges you for each GET request. If your stream is divided into multiple chunks, you’ll need to make multiple GET requests to read all the events. While each GET request might not be expensive on its own, the costs can accumulate quickly, especially as your streams grow and you have to read through more and more chunks.&lt;/p&gt;
&lt;h2 id=&quot;snapshots-to-the-rescue-yuk&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#snapshots-to-the-rescue-yuk&quot; aria-label=&quot;snapshots to the rescue yuk permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Snapshots to the rescue, yuk…&lt;/h2&gt;
&lt;p&gt;Here comes the part that I’m afraid of the most. I spend months explaining not to use snapshots. E.g. through articles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/keep-your-streams-short-temporal-modelling-for-fast-reads-and-optimal-data-retention/&quot;&gt;Keep your streams short! Temporal modeling for fast reads and optimal data retention&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/closing_the_books_in_practice/&quot;&gt;Implementing Closing the Books pattern&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/should_you_always_keep_streams_short/&quot;&gt;Should you always keep streams short in Event Sourcing?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;/en/event_streaming_is_not_event_sourcing/&quot;&gt;Event Streaming is not Event Sourcing!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Or in the talk:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/gG6DGmYKk4I?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLDR. Snapshots are performance optimisation, a cache. As with each type of cache, they should be used with caution.&lt;/strong&gt; The idea of a snapshot is to periodically store a full representation of the current state of an entity so that you don’t have to replay all events from the beginning every time you need to rebuild the state. If you’re using a traditional database, this is most often premature optimisation, as events are small, and it doesn’t cost much to read events. Thanks to that, we’re not falling into cache invalidation issues and other mishaps I outlined in linked articles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, in our S3-based approach, snapshots can actually be a cost-effective solution.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;By adding a snapshot to each chunk, we can dramatically reduce the number of GET requests&lt;/strong&gt; required to read the full state of an entity. Instead of reading all the events from the beginning, we can read the latest chunk containing the most recent snapshot and subsequent events. This way, even if the stream has hundreds or thousands of events, you only need to retrieve the latest chunk to get the current state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Since S3 charges per request rather than per data transfer&lt;/strong&gt; (assuming your infrastructure, like AWS Lambda or EC2, is also hosted within AWS), combining events and snapshots into a single chunk can lead to significant cost savings. You’re minimising the number of requests while still keeping your data access efficient.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Of course, there’s a trade-off to this approach.&lt;/strong&gt; Each snapshot increases the size of your chunk files, slightly raising your S3 storage costs. If your snapshot becomes invalid—perhaps because of a schema change in your application—you’ll need to read all the events from the beginning of the stream to rebuild the state. This can negate the cost savings we’ve discussed, so managing this carefully is important.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One way to mitigate this risk is by versioning your snapshot schema.&lt;/strong&gt; When you store a snapshot, include the schema version alongside it. Your application can then check if the stored snapshot’s schema version matches the current schema version in your code. If they match, you can safely use the snapshot. If they don’t, you’ll need to replay all events to rebuild the state and store a new chunk with the updated schema in the next chunk.&lt;/p&gt;
&lt;p&gt;For that our chunk file name could be:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;{streamPrefix}/{streamtType}/{streamId}/{streamVersion}.{chunkVersion}&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Having the example series of chunks:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ecommerce/orders/12345/001.000.json
ecommerce/orders/12345/002.000.json
ecommerce/orders/12345/003.000.json
ecommerce/orders/12345/004.000.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After merging those chunks and reconciliation, we’ll get a new file:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;ecommerce/orders/12345/004.001.json&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’ll contain the rebuild snapshots and possibly all events from the previous chunks, allowing you to delete the old ones and reducing the storage cost.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/16eed8067d1682e328c87e2e8dc0f86e/48c04/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAABX0lEQVR42qVT25bCIAz0X1bXQikUyq3VXf//q+IkqOtxtS99yKENyWRmgF2nMz2H6gv1wyyhTSbnE5V6ppQXMsMk+9pUWV97OXaqT/iIAChk7EyDQ6Ot+K9kHQMnRKbeJOQLcosM4oFcz7UMzjiqB2CnLA3WUi5V1k6P2HDkRgdmLafNBMBAow/IzQB1YBslplRpXn5RH2Xwjtl1erqxyrfpRb59OAEIMrHf6YCaRGNg6VnYsQrnF7LjIiobQ81e3P1o/jSPeJ3FN+swcPDCqB8W2W8eV+k7qicP3xnb6fQI3QcwXcAEQ8CSc0f1F/8O5T3gPSIdvhW84wPx+NYia61nFZCbjc3Czo1VTnydwCpggaQAVnvy3svJfh32Nys2MOy0F2baRPFwk2Rmw5Jj/sF9O2+V3Bhq0+6eG9sL2Xwog6t4RReaYmP46Q0/AJ/v1GvcJZd6oZhO4mO7yJ/rrzAFrzu4AGqQAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/16eed8067d1682e328c87e2e8dc0f86e/507e6/image-3.webp 200w,
/static/16eed8067d1682e328c87e2e8dc0f86e/28a80/image-3.webp 400w,
/static/16eed8067d1682e328c87e2e8dc0f86e/8d2ea/image-3.webp 800w,
/static/16eed8067d1682e328c87e2e8dc0f86e/eb685/image-3.webp 1114w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/16eed8067d1682e328c87e2e8dc0f86e/36ca5/image-3.png 200w,
/static/16eed8067d1682e328c87e2e8dc0f86e/a3397/image-3.png 400w,
/static/16eed8067d1682e328c87e2e8dc0f86e/a331c/image-3.png 800w,
/static/16eed8067d1682e328c87e2e8dc0f86e/48c04/image-3.png 1114w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/16eed8067d1682e328c87e2e8dc0f86e/a331c/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Following this convention, you can quickly identify the latest chunk using the highest chunk version within the current stream version. This simplifies the process of retrieving and updating your data.&lt;/p&gt;
&lt;p&gt;Yet, we also need to clear those unused chunks at some point, which means we need to do DELETE operations for each chunk file. We can also optimise the storage costs by benefiting from various storage classes like &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/intelligent-tiering-overview.html&quot;&gt;S3 Intelligent-Tiering&lt;/a&gt; and &lt;a href=&quot;https://aws.amazon.com/s3/storage-classes/glacier/&quot;&gt;S3 Glacier&lt;/a&gt;, which allow us to move automatically not-accessed data to lower-cost storage (and old chunks won’t be accessed).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Managing snapshots and handling schema migrations can add complexity to your system.&lt;/strong&gt; Storing snapshots increases your storage needs, but the reduced number of GET requests often offsets this. There’s no free lunch!&lt;/p&gt;
&lt;h3 id=&quot;two-points-for-griffindor-s3-real-time-notifications&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#two-points-for-griffindor-s3-real-time-notifications&quot; aria-label=&quot;two points for griffindor s3 real time notifications permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Two points for &lt;del&gt;Griffindor&lt;/del&gt; S3: Real-Time Notifications&lt;/h3&gt;
&lt;p&gt;One of Amazon S3’s compelling features is its &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventNotifications.html&quot;&gt;ability to send notifications&lt;/a&gt; when objects are created or updated, making it an excellent fit for our case. When a new event or a batch of events is added to an S3 bucket, S3 can trigger notifications through Amazon SNS (Simple Notification Service) or Amazon SQS (Simple Queue Service). These notifications allow you to process new data in near real-time without needing to poll the S3 bucket constantly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When you upload a new chunk file—say,&lt;/strong&gt; &lt;em&gt;&lt;strong&gt;ecommerce/orders/12345/002.000&lt;/strong&gt;&lt;/em&gt;&lt;strong&gt;—S3 can be configured to publish an event notification that a new object has been added.&lt;/strong&gt; This notification can trigger a Lambda function or send a message to an SQS queue, allowing your system to react to the new data immediately.&lt;/p&gt;
&lt;p&gt;However, the S3 notification itself doesn’t contain all the event data. It simply informs you that a new object (or chunk) has been created or updated. You’ll need to retrieve the object from S3 to access the actual events data.&lt;/p&gt;
&lt;p&gt;Given that each chunk can include multiple events, snapshots, and metadata, it’s often unnecessary to load the entire chunk, especially if you only need the latest events. This is where &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/selecting-content-from-objects.html&quot;&gt;S3 Select&lt;/a&gt; comes in handy. S3 Select allows you to query a subset of data from within an object, meaning you can pull only the specific events you need without downloading the whole file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In regular usage, when an application needs to rebuild an entity’s current state, you can use S3 Select to load just the snapshot from the latest chunk.&lt;/strong&gt; This minimises the amount of data you need to load, as the snapshot already represents the latest state of the entity. When the snapshot is outdated or invalidated (for example, after a schema change). In that case, you can retrieve all events starting from the point after the last valid snapshot to rebuild the state accurately.&lt;/p&gt;
&lt;h2 id=&quot;why-choose-s3-over-traditional-databases&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-choose-s3-over-traditional-databases&quot; aria-label=&quot;why choose s3 over traditional databases permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Why Choose S3 Over Traditional Databases?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Amazon S3 is known for its virtually unlimited scalability and 99.999999999% durability, which makes it an attractive option for building distributed syste&lt;/strong&gt;ms. Unlike traditional databases that may struggle with scaling up to handle massive amounts of data, S3 is built to handle enormous datasets efficiently. This makes it a strong foundation for event sourcing, where data grows over time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;S3’s ability to store data in various formats (JSON, Parquet, Avro) and integrate with other tools like AWS Lambda, AWS Glue, and DuckDB opens up a world of possibilities.&lt;/strong&gt; For example, DuckDB can efficiently scan through multiple Parquet files stored in S3, enabling complex analytics directly on your event data without the need to move it to another storage solution. This flexibility is why Turso chose S3 to back its globally distributed SQLite-based storage, leveraging S3’s strengths to handle multi-region, low-latency data access.&lt;/p&gt;
&lt;h2 id=&quot;cool-stuff-but-how-much-will-it-cost&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cool-stuff-but-how-much-will-it-cost&quot; aria-label=&quot;cool stuff but how much will it cost permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cool stuff, but how much will it cost?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;S3 is incredibly cost-effective for workloads that involve large data volumes but relatively few requests, which aligns well with systems like Warp Stream.&lt;/strong&gt; As a streaming solution, Warp Stream can batch events, reducing individual PUT and GET requests. This batching, combined with S3’s cost model, makes managing large-scale event streaming cheaper than traditional databases that might charge based on the number of transactions.&lt;/p&gt;
&lt;p&gt;Our case is different tho, as we cannot easily batch operations. Event Store are databases, so they access and store granular data. We’ll have a lot of small files, which is not super optimal for the current S3 design.&lt;/p&gt;
&lt;p&gt;While S3’s new conditional writes open the door for optimistic concurrency control, how you structure your data, name your files, and manage versions will significantly impact both performance and cost.&lt;/p&gt;
&lt;p&gt;While S3 has many advantages, there are scenarios where e.g. DynamoDB might be a better fit:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Real-Time Query Performance:&lt;/strong&gt; If your application requires low-latency, real-time queries with immediate consistency guarantees, DynamoDB is likely the better choice. DynamoDB’s millisecond response times and support for complex queries make it ideal for use cases where performance is critical.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;High-Frequency, Small Transactions:&lt;/strong&gt; DynamoDB is designed for high-frequency, small transactions. If your system performs a large number of small read/write operations, DynamoDB’s pricing model could be more cost-effective than S3. With S3, the cost per request can add up quickly, especially if you have many small, frequent transactions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Built-In Concurrency Controls:&lt;/strong&gt; DynamoDB natively supports optimistic concurrency control with its &lt;em&gt;&lt;a href=&quot;https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html&quot;&gt;ConditionalExpression&lt;/a&gt;&lt;/em&gt; feature, which allows you to enforce constraints on updates to ensure that only one operation can succeed when multiple transactions are attempted simultaneously. This can be simpler to implement than S3, where you need to manually manage versioning and naming strategies.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We also need to do math, which is hard with the complex AWS pricing strategy. It may appear that, in your case, DynamoDB is actually cheaper.&lt;/p&gt;
&lt;p&gt;Read also my detailed investigations in:&lt;/p&gt;
&lt;p&gt;[&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 56px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC80lEQVR42o2VO0hbYRTHr4nvJ6VK1fiig7YIobo4ZDAWEgfpIg4+0aWDQ8DFki0FFReLLipOIopgfEDRRR0FBxdRR4cKVXAQI4nGmHu9/55zki9VWmM+OPnuvd/5fuf/Pc6JBsCiUbu8vHz38PDw1TAMbzQaHab+WzIjH9UPB4NBBzP8fr+Vey0UCtlp8BdizUTqzVT+JOa7phrB9uMOETI9HA7r3CezSCSinqOPj48GTyZhn7Wbm5v3BAyqiB6PBwMDA/KytbWFi4uL2IAZE06+0i8uLsLpdOLq6gpxMO7v739o1NfRPoT4w8bGhkmC0d3dLZOampqwvb39DKTregLIvkNDQzLMP7e3tzMaSa+j9d/xh/HxcQH29/fLpMHBQezvx3aDovM+4e5OXLGwsCBAl8uVUEhb9X9gb2/vM1VPm1I4Pz8vwLa2tpeBo6OjZnp6OmpqatDZ2SlgVsvPHR0d8tzX1ye93W5HdnY2WltbXwaOjIyYmZmZKC8vF2c2VsEB6uvrYbFYwONZWVkoLi5GQUEB3G53cmBGRgYqKytRXV2NoqIiTE5O4vT0FIeHh9jd3UVFRYUY++Tn578OZAUMy8nJQVdXF46Pj9HY2Ija2losLy9jenoavC1VVVWpA3mJvNTZ2Vl4vV6MjY3h6OgIDocDe3t7slRW+SqQD+Wpwp6eHlkqHwArXFpawtzcHHhblMJXD4UPgqOrg5mYmMDJyYko29nZQWlpKUpKSmCz2URp0ns4NTUl95CjM1Atq6GhAc3NzQIoKysTGB8K72V7e/s/wA90gSX1rq+vzZaWFlHGILbCwkJYrVakpaXJO0Pz8vKQm5srgQ8ODhKpR6wZjZLbRtUioIoDFQtsbm5iZWUFq6urYmtra1hfX0+8Kzs7O1MZpceBPlW+/PyBwDpVFSMeMSUz4vlJc8ME/ChAUvWGKs5P42/yplpkTS5rNO035fgXgfl8Pgv/DbAFAoFPNMAXy5WiuakKOc/Pz9+qv4A/x/GAJi+lEOYAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/61627/image-4.webp 56w&quot;
              sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-4.png 56w&quot;
            sizes=&quot;(max-width: 56px) 100vw, 56px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/12db1a0ede1b3d7e7b7a86d6ce7da18b/a7ea8/image-4.png&quot;
            alt=&quot;image 4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;Architecture Weekly&lt;/p&gt;
&lt;p&gt;Show me the money! Practically navigating the Cloud Costs Complexity&lt;/p&gt;
&lt;p&gt;In my last article, I explored how Amazon S3’s new conditional writes feature can be used to implement a strongly consistent event store. I think that this feature opens up powerful architectural possibilities for distributed systems. But as much as we’d love to dive right into the code, there’s a bigger question to answer first: how much will it cost…&lt;/p&gt;
&lt;p&gt;Read more&lt;/p&gt;
&lt;p&gt;2 years ago · 1 like · Oskar Dudycz&lt;/p&gt;
&lt;p&gt;](/en/show-me-the-money-practically-navigating/)&lt;/p&gt;
&lt;h2 id=&quot;verdict-yay-or-nay&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#verdict-yay-or-nay&quot; aria-label=&quot;verdict yay or nay permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Verdict: yay or nay?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;S3’s evolution from simple file storage to a robust, scalable backbone for distributed systems is driving a new wave of cloud-native tools.&lt;/strong&gt; Introducing conditional writes is a significant step forward, enabling more sophisticated use cases like optimistic concurrency control. While there are still limitations—such as the lack of an `If-Match` header—S3’s flexibility, scalability, and integration with other AWS services make it an increasingly attractive option for modern architectures.&lt;/p&gt;
&lt;p&gt;We’re already seeing companies like Turso and Warp Stream take advantage of these capabilities, building innovative solutions that push the boundaries of cloud storage. As AWS continues to enhance S3’s feature set, we’ll likely see even more tools and platforms built on top of this versatile service, further pushing its role as a foundational piece of modern cloud infrastructure.&lt;/p&gt;
&lt;p&gt;Whether you’re building event-driven architectures, distributed databases, or advanced analytics platforms, understanding the nuances of S3’s capabilities and costs will be crucial. With the right strategies in place, S3 can offer both the performance and cost-effectiveness you need to build robust, scalable systems.&lt;/p&gt;
&lt;p&gt;Still, seeing how S3 advances, we may see a next step, offering more advanced conditional writes and that can totally break the status quo. If S3 were to support an `If-Match` header, it would allow developers to implement true optimistic concurrency control, where updates are only applied if the current version matches the expected version. This would reduce the likelihood of conflicts and errors in distributed systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With enhanced concurrency controls, we could see more cloud-native tools and platforms built directly on top of S3.&lt;/strong&gt; Tools like Turso and Warp Stream are already leveraging S3 for its scalability and flexibility, but additional features like `If-Match` could further solidify S3 as a core component in more sophisticated cloud architectures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m considering trying to implement such an event store with Emmett. I already support PostgreSQL and EventStoreDB, so why not try what I wrote above? If you’re curious about the effort, ping me! Especially if you’d like to use or sponsor such an event store!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You don’t know what’s &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt;? Check &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;docs&lt;/a&gt;, and join our &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Discord server&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;If you’d liked this article, please subscribe; I’m doing such deep dives every week!&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[My Architecture Drivers]]></title><description><![CDATA[I don’t feel like an authority or an expert. I prefer to think about myself as a practitioner. Our industry is filled with self-proclaimed…]]></description><link>https://event-driven.io/en/architecture_drivers/</link><guid isPermaLink="false">https://event-driven.io/en/architecture_drivers/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 750px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d3ac5b456d98d37dcfd581c62fcf25d1/b13e1/2024-08-31-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4jAAAuIwF4pT92AAADIUlEQVR42g3L3U8TBwAA8Psbthjilphppi6UuRHUigKl0krhoN93x125ckehvbt+93p3vS9a2isthVKhtrZSqfFzi4qo8QtjjA9m2bLswfiwJ30yxgdft2QPZf7ef4DEMiJLV1ezl2ul+ory8sGlzn8fOp1Pnc7Hvb3Pb/960loTd+9t3b12YXMjv7GSvtpcLWvC+rJSX8sCIWxMY4nNda1Wzl6paQ9bmdePWh/e/fbvP+/3Oh+f3G3cv1HbudW4vlleKyiVYvpyrZCTwktqrJBmAWvPIcZljJKutYK6VdVe3anca+V+beRebm+++f3R0+2tK/Viu15o14tSIpBX4xulBU0OalIoGsAB29lBFJrwYdZGtdSoZNczVL2QuKCxD1qrLx5eWxBCFAkxJOSfgSDriBd1JhgyQMAZKeHD3YDj+AHzsW9P/HRU5cNZOeF3nxMIa0mig24wNe22Dp+yD/RZjf0QaHQM9zotw+EZlPaicZoYOPkzgJ85OH6sC5kwZdUUH2OQMfMlMX5Ridr6+zQ/3hbCyzjMI/agCwzYR3HQMgONGgf1iNM+ZhwE4MkxAjyLgWaG8PhtJt/oEIdDYcRhOqNH+09xXqRVXiyn+fNC7HyUWcLRtoyFnaOEyfjDoYOAzThAo7DbcHLaDspRxnz6eO+Rw0i3btLhAoeMFdJzu12t1VeWNEWUuIws5AWhEgvejPqb1CzQta9r/3ff60/rXDbTOGjVd31DmUdyAndD173b3Hjmgf9IS0vM3ILyZfJ8MiYqEifLdCScZ3yA3jCE9fXmAOCXeOTxq93nP/bs2G3rF6v0hGU3m3qLOR73dBt0uiQXU0WWY2OKLBZLBSElzgUo4M+/3+yI3K2vvl7GUDm3OId7SS9OwVBzGm6aDVOGQcQ8QtNzEh/jEhE2HlFVeWW1lJIkmqIBGIahKdTuxd0oimPo/DxJzRN52rfNBDTLue7DRxLJaFpieTaUSka/5MVsJl/Isywb8AcAi8Xidjl9M9MeBPIR+Czhofwk5pmatNsQcNwzhUSCfj4RkoS4LCYVRVwuFdX0AsMEfeTs/yFyVOSxeVU+AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d3ac5b456d98d37dcfd581c62fcf25d1/507e6/2024-08-31-cover.webp 200w,
/static/d3ac5b456d98d37dcfd581c62fcf25d1/28a80/2024-08-31-cover.webp 400w,
/static/d3ac5b456d98d37dcfd581c62fcf25d1/db9b4/2024-08-31-cover.webp 750w&quot;
              sizes=&quot;(max-width: 750px) 100vw, 750px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d3ac5b456d98d37dcfd581c62fcf25d1/36ca5/2024-08-31-cover.png 200w,
/static/d3ac5b456d98d37dcfd581c62fcf25d1/a3397/2024-08-31-cover.png 400w,
/static/d3ac5b456d98d37dcfd581c62fcf25d1/b13e1/2024-08-31-cover.png 750w&quot;
            sizes=&quot;(max-width: 750px) 100vw, 750px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d3ac5b456d98d37dcfd581c62fcf25d1/b13e1/2024-08-31-cover.png&quot;
            alt=&quot;2024 08 31 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I don’t feel like an authority or an expert. I prefer to think about myself as a practitioner.&lt;/strong&gt; Our industry is filled with self-proclaimed experts; we need more doers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For similar reasons, I was reluctant to call myself an “Architect” for a long time.&lt;/strong&gt; Somehow, it became pejorative, but should it be? I changed my mind thanks to &lt;a href=&quot;https://www.linkedin.com/in/jpalka/&quot;&gt;Jarek Pałka&lt;/a&gt;, who says that “we all are architects”. It’s a simple line but multidimensional. That means (to me) that we all are responsible for making our systems work as they should. It’s not about bragging but accountability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, I never feel comfortable being asked about Software Architecture Drivers.&lt;/strong&gt; How do you give someone a checklist for good software? Yet I had to answer this time, as &lt;a href=&quot;https://www.linkedin.com/in/jedrzejewski-maciej/&quot;&gt;Maciej Jędrzejewski&lt;/a&gt; asked me if I could write my thoughts &lt;a href=&quot;https://leanpub.com/master-software-architecture&quot;&gt;to his new book&lt;/a&gt;. How could I reject the offer? Actually, I could, as it surprised me, but hey, maybe my few words will help others fight their impostor syndrome. Or it was just too flattering. Nevertheless, here they are!&lt;/p&gt;
&lt;p&gt;It’s funny that we called our industry &lt;strong&gt;SOFTware&lt;/strong&gt;, but it’s all about making &lt;strong&gt;HARD decisions&lt;/strong&gt;. Usually, we make them when we’re the dumbest: we don’t know the business domain, we don’t know the user needs, and we are unsure of technology choices. Plus, even if we do, our changing environment is open to proving us wrong.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For me, architecture decisions are more a process than a set of specific rules. It’s a process of answering the following questions:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WHY?&lt;/strong&gt; So, understanding the product vision and business model. Consider &lt;a href=&quot;/en/follow_the_money/&quot;&gt;where the money flows&lt;/a&gt;: who the client and the user are. That’s an important fact, as we should care about all users but optimise for clients, especially those who bring money. In the end, our product should bring money.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WHAT?&lt;/strong&gt; Understand what we actually need to build. Set a mental model of the business workflow. This is an excellent moment for collaborative tooling, brainstorming and modelling practices like Event Storming, Domain Storytelling, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HOW?&lt;/strong&gt; Think about the requirements and guarantees you need to have. Find architecture patterns and class of solutions that will fulfil your requirements. So, the type of databases, deployment type, integration patterns, not the specific technologies. Consider tools like C4 and other tools to structure your findings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WITH.&lt;/strong&gt; Select the tooling based on the outcome of the previous point. It has to fulfil requirements, but also non-functional like costs, match team experience, ease of use.
Then rinse and repeat.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Architecture is not created in a vacuum.&lt;/strong&gt; Talk and collaborate with business, users and your technical fellows.&lt;/p&gt;
&lt;p&gt;Consider the &lt;a href=&quot;/en/on_the_importance_of_shaping_the_boundaries_in_team_management/&quot;&gt;team you (can) have&lt;/a&gt;. Most of the time, the best technology is the one that your team knows. We’re building new tools, but to be true, rarely sophisticated ones. Most of them are regular lines of business applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And hey, let me share the secret with you: your decisions will be wrong. Mine also.&lt;/strong&gt; And that’s fine. We don’t need to be flawless; our system also doesn’t need to be. Expect the change; it’ll come.&lt;/p&gt;
&lt;p&gt;So &lt;a href=&quot;/en/why_are_we_afraid_of_our_decisions/&quot;&gt;don’t be afraid to make decisions&lt;/a&gt;, but don’t rush yourself. Always consider alternative solutions. Record your decisions together with thrown away ideas. Provide the context and explain WHY, WHAT, HOW, WITH. Provide the assumed limits. Suggest how to evolve if, e.g. your system will be a huge success and becomes overwhelmed by traffic. Some problems are good to have. But don’t need to be solved immediately.&lt;/p&gt;
&lt;p&gt;We should &lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;optimise not for maintainability but for removability&lt;/a&gt;. If our system is built so that we can relatively easily remove pieces from it, then we can drop bad ideas and move on to new ones. Also, by accident, we’re getting a system that’s easier to maintain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What are your architecture drivers? Or better, what’s your process?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check also Maciej’s book &lt;a href=&quot;https://leanpub.com/master-software-architecture&quot;&gt;“Master Software Architecture Book”&lt;/a&gt;.&lt;/strong&gt; I’m still in front of reading it till the end. But I like what I see so far; the outline and range of topics show the holistic, actionable vision of building software. And, most importantly, it’s not pompous.&lt;/p&gt;
&lt;p&gt;I think getting more books/talks like that would be great. We need more books in the spirit of “this is my story, this is why I think it’s important and worked out for me. “. It’s good that it’s from a personal perspective, allowing us to compare or benchmark it to our vision and experience.&lt;/p&gt;
&lt;p&gt;If you need even more architecture benchmarks, I have gathered some of my past articles on my general approach to architecture and software design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/architect_manifesto/&quot;&gt;Architect Manifesto&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_design_software_architecture_pragmatically/&quot;&gt;How to design software architecture pragmatically&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/removability_over_maintainability/&quot;&gt;Removability over Maintainability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;en/the_risk_of_ignoring_risks/&quot;&gt;The risk of ignoring risks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/why_are_we_afraid_of_our_decisions/&quot;&gt;Why are we afraid of our decisions?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/chesterton_fence_and_software_architecture/&quot;&gt;What do the British writer and his fence have to do with Software Architecture?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/follow_the_money/&quot;&gt;Follow the money to get a better design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/a_few_words_on_communication/&quot;&gt;A few words on communication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_successfully_do_documentation_without_maintenance_burden/&quot;&gt;How to successfully do documentation without a maintenance burden?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/the_magic_is_that_there_is_no_magic/&quot;&gt;The magic is that there is no magic. Or how to understand design patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_solve_complicated_problems/&quot;&gt;Not all issues are complex, some are complicated. Here’s how to deal with them&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/on_the_importance_of_shaping_the_boundaries_in_team_management/&quot;&gt;On the importance of setting boundaries in team management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/dune_and_long_term_goals/&quot;&gt;What Dune can tell us about setting our goals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/stacking_the_bricks/&quot;&gt;Stacking the bricks in the software development process&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/holy_graal_syndrome/&quot;&gt;The Holy Grail syndrome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/dive_a_bit_deeper_look_a_bit_wider/&quot;&gt;Dive a bit deeper, look a bit wider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/what_does_mr_bean_opening_the_car_have_to_do_with_programming/&quot;&gt;What does Mr Bean opening the car have to do with programming?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/what_does_a_construction_failure_have_to_do_with_our_authorities/&quot;&gt;What does a construction failure have to do with our authorities?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;And check &lt;a href=&quot;https://www.architecture-weekly.com/&quot;&gt;Architecture Weekly&lt;/a&gt; where I’m showing my less-event-driven face every week!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Webinar #21 - Michael Drogalis: Building the product on your own terms]]></title><description><![CDATA[Did you have a brilliant idea for a startup but were afraid to try it? Or maybe you’ve built an Open Source tool but couldn’t find a way to…]]></description><link>https://event-driven.io/en/webinar-21-michael-drogalis-building/</link><guid isPermaLink="false">https://event-driven.io/en/webinar-21-michael-drogalis-building/</guid><content:encoded>&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Webinar recording&quot; src=&quot;https://www.youtube-nocookie.com/embed/JWoMWF4nVNU?rel=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Did you have a brilliant idea for a startup but were afraid to try it?&lt;/strong&gt; Or maybe you’ve built an Open Source tool but couldn’t find a way to monetise it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to be a solopreneur, a company of one?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What’s so special about building a product that mimics your production traffic to Kafka, S3, Postgres, and more?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You can find that out thanks to a special guest, &lt;a href=&quot;https://michaeldrogalis.substack.com/&quot;&gt;Michael Drogalis&lt;/a&gt;!&lt;/strong&gt; Michael told us his story on building the product on your own terms. He shared many insights on how to start your journey, monetise your work, rewire your thinking and get your first customer. And beyond!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A year ago, he made a bold move and started his new journey:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;[&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e3c98eb0b1aa9dec8f082f1f187eddc3/26c69/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAAEz0lEQVR42gHEBDv7AOz29eny8ejy8d/m4sWsnK+BbqV0Y6VxXqx2Ya99Z8i+sfD59eLZzpZyWqqjltzc0+bn3dXRwqaJb8e8qwDq9PPo8vHn8/LLwbfGn42wgW+falmiZ1avcVy9fmSwgWrX2dDb2M11XkdHPS5USTp9cF2FZE2ab1PVz8EA6PTy5fHw3+nmzryvx56QpXNjkVpKl1tLomRRtHNasm9Ts5+PnJiLVEMuZ1dDZE84UDwkdVA3u6WS2tzQAOT19uHw8Nzq6NbHu86onal3aZBZSZlfT6ZoVbx1Xb50WXxdSk1DNVM/KWpROFJCLlZCLGNON8vJv9DRxgDE08TJ29HJ0cvKtqi5l4iYaVmTVkSSVUKbWkWaXUW1clZwRzNSQTB3WEFvTzd5XEBlSC5iUkDLzcbIycIAoq2GnqyGn6CA0LChqYd1UzEhlGRUqm1Yd0EsVi0cpmVIiVU+OyUYak46mXddmXlbXUEnem5dx8/IvcO7AK25kqezjJ+mgdGzo9KypIJURq2BdcB/cJJRPI9RPL5zV5ZZQj0jEkYqFlM+K2BLN0QvGWxfTMbRzMjT0ACzv5euupGlsYfHuZ/ctamscWPQoZS9gXCoYEetXkrLemF5SDNLLxtUNR5gQShLOSg1IhJyYU3c7u3i9vUAuMSas7+VrbmOrbiP1bekm1dDzKKTv4d3pWBGnVY+vG9SWDgkVzskgFIxlWU+cFM3W0Epe2ZOytzZytrXAL7KoLnFm7W/lq27kcS2laFrVMGQg6pkVZNLNppYQJxePUkrGVk9JnBLLpRpRZFwT25TOGJQPK24sLTCuwDE0KXBy6G7xJu3wZa5vZW4kHTRno60Zl6aRjawakyGTjFCJBRiQylaPCN8WjxfSjY+LRxOPiqxuLHC0csAy9Wpx8+lx86iwsiefIh7iIJ/3racwYhwpFxCpWRFgEgvRCITRzAbY0UqX0czIBkSPi0aX1RGx9LMw8/IANDZrMvUprbAn3N9eT5IVEFKVdG1qMyZgJxZP5RWO4ZIM0YlFllDLWBHLjEoIyghG0k5JlhNQ7e/vMrX0ADX3q27x6xpeYVETFU6PkMaHB94amrsvq27emShVjxxPCspGRBuVjxnTjQrIx4qJSBIOihHNyiipaHQ3dcAyte6iJ2rY3KBP0FENTc6JiYmJigrdl9cmmhabDopKhoUDgsKhnFYqYpmZk86HRwdLSchMysmi42M0NzYAI+epWZrcURGSjMzNTIyMysqKyUkJx8fIiQfHhwWFRsWFBMPEmJURrmeeYZrSywmJCYnKzAuMXl5ec7a1wBeZGpHRUI0MTEsKiovLi4tKysqKSomIiMlHx8iHBokISEgGxsdFRSZhWyxlnNUQi8nKCs0MjRlY2PI1NIASElLMzAvMS0rLCYlLSgmLCgoLSkrKy8xKDM1JikrKyksIx0bFg4OQzcwyLCMs4xjX09BJSkwUE9Ru8XEADEyNC0pKCkmJTQ0LzYuKSskJC8xMjVLTi46Pi08QC4wMiEZFiQbGSAVEIV1YKKDYZBxVn5iVIlvYa60sgCPdmVORT5oLCfGZVRUSkAnISAzOzs4U1c5Z20vQEUuLzAhFxQpIB4eEw1cTkKbh26WblLioHvoonq/saX7uhWQeH3NcwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/e3c98eb0b1aa9dec8f082f1f187eddc3/507e6/image-2.webp 200w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/28a80/image-2.webp 400w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/8d2ea/image-2.webp 800w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/68fc1/image-2.webp 1200w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/4b075/image-2.webp 1280w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/e3c98eb0b1aa9dec8f082f1f187eddc3/36ca5/image-2.png 200w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/a3397/image-2.png 400w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/a331c/image-2.png 800w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/8537d/image-2.png 1200w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/26c69/image-2.png 1280w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/e3c98eb0b1aa9dec8f082f1f187eddc3/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;Michael Drogalis&lt;/p&gt;
&lt;p&gt;Week 1: I said I’d do what?!&lt;/p&gt;
&lt;p&gt;Look, it’s a universal truth: the coolest part about starting a company is telling people you’re starting a company. Starting four startups in four quarters is especially cool…&lt;/p&gt;
&lt;p&gt;Read more&lt;/p&gt;
&lt;p&gt;3 years ago · 12 likes · Michael Drogalis&lt;/p&gt;
&lt;p&gt;](&lt;a href=&quot;https://michaeldrogalis.substack.com/p/week-1-i-said-id-do-what?utm_source=substack&amp;#x26;utm_campaign=post_embed&amp;#x26;utm_medium=web&quot;&gt;https://michaeldrogalis.substack.com/p/week-1-i-said-id-do-what?utm_source=substack&amp;#x26;utm_campaign=post_embed&amp;#x26;utm_medium=web&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That ended up launching &lt;a href=&quot;https://shadowtraffic.io/&quot;&gt;ShadowTraffic&lt;/a&gt;: a containerized service for declaratively generating data, packed with knobs to mimic your production traffic to Kafka, S3, Postgres, and more.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This webinar will be an excellent chance to learn from Michael and his journey in building your product. It’s especially noteworthy that this is not his first venture. In 2013, he started an open-source stream processing project. Then, in 2016, he &lt;a href=&quot;https://www.crunchbase.com/organization/distributed-masonry&quot;&gt;co-founded a company&lt;/a&gt; based on that project and built the first version of infinite storage for Apache Kafka. Then, Confluent acquired it in 2018.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We’ve also learned more about ShadowTraffic and why it’s special.&lt;/strong&gt; I think the product is an exciting idea in the event streaming space, solving people’s real problems.&lt;/p&gt;
&lt;p&gt;Big KUDOS to Michael for being so open on his journey and sharing all those insights.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Follow Michael on substack; he’s sharing his insights every week:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;[&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e3c98eb0b1aa9dec8f082f1f187eddc3/26c69/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAAEz0lEQVR42gHEBDv7AOz29eny8ejy8d/m4sWsnK+BbqV0Y6VxXqx2Ya99Z8i+sfD59eLZzpZyWqqjltzc0+bn3dXRwqaJb8e8qwDq9PPo8vHn8/LLwbfGn42wgW+falmiZ1avcVy9fmSwgWrX2dDb2M11XkdHPS5USTp9cF2FZE2ab1PVz8EA6PTy5fHw3+nmzryvx56QpXNjkVpKl1tLomRRtHNasm9Ts5+PnJiLVEMuZ1dDZE84UDwkdVA3u6WS2tzQAOT19uHw8Nzq6NbHu86onal3aZBZSZlfT6ZoVbx1Xb50WXxdSk1DNVM/KWpROFJCLlZCLGNON8vJv9DRxgDE08TJ29HJ0cvKtqi5l4iYaVmTVkSSVUKbWkWaXUW1clZwRzNSQTB3WEFvTzd5XEBlSC5iUkDLzcbIycIAoq2GnqyGn6CA0LChqYd1UzEhlGRUqm1Yd0EsVi0cpmVIiVU+OyUYak46mXddmXlbXUEnem5dx8/IvcO7AK25kqezjJ+mgdGzo9KypIJURq2BdcB/cJJRPI9RPL5zV5ZZQj0jEkYqFlM+K2BLN0QvGWxfTMbRzMjT0ACzv5euupGlsYfHuZ/ctamscWPQoZS9gXCoYEetXkrLemF5SDNLLxtUNR5gQShLOSg1IhJyYU3c7u3i9vUAuMSas7+VrbmOrbiP1bekm1dDzKKTv4d3pWBGnVY+vG9SWDgkVzskgFIxlWU+cFM3W0Epe2ZOytzZytrXAL7KoLnFm7W/lq27kcS2laFrVMGQg6pkVZNLNppYQJxePUkrGVk9JnBLLpRpRZFwT25TOGJQPK24sLTCuwDE0KXBy6G7xJu3wZa5vZW4kHTRno60Zl6aRjawakyGTjFCJBRiQylaPCN8WjxfSjY+LRxOPiqxuLHC0csAy9Wpx8+lx86iwsiefIh7iIJ/3racwYhwpFxCpWRFgEgvRCITRzAbY0UqX0czIBkSPi0aX1RGx9LMw8/IANDZrMvUprbAn3N9eT5IVEFKVdG1qMyZgJxZP5RWO4ZIM0YlFllDLWBHLjEoIyghG0k5JlhNQ7e/vMrX0ADX3q27x6xpeYVETFU6PkMaHB94amrsvq27emShVjxxPCspGRBuVjxnTjQrIx4qJSBIOihHNyiipaHQ3dcAyte6iJ2rY3KBP0FENTc6JiYmJigrdl9cmmhabDopKhoUDgsKhnFYqYpmZk86HRwdLSchMysmi42M0NzYAI+epWZrcURGSjMzNTIyMysqKyUkJx8fIiQfHhwWFRsWFBMPEmJURrmeeYZrSywmJCYnKzAuMXl5ec7a1wBeZGpHRUI0MTEsKiovLi4tKysqKSomIiMlHx8iHBokISEgGxsdFRSZhWyxlnNUQi8nKCs0MjRlY2PI1NIASElLMzAvMS0rLCYlLSgmLCgoLSkrKy8xKDM1JikrKyksIx0bFg4OQzcwyLCMs4xjX09BJSkwUE9Ru8XEADEyNC0pKCkmJTQ0LzYuKSskJC8xMjVLTi46Pi08QC4wMiEZFiQbGSAVEIV1YKKDYZBxVn5iVIlvYa60sgCPdmVORT5oLCfGZVRUSkAnISAzOzs4U1c5Z20vQEUuLzAhFxQpIB4eEw1cTkKbh26WblLioHvoonq/saX7uhWQeH3NcwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/e3c98eb0b1aa9dec8f082f1f187eddc3/507e6/image-2.webp 200w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/28a80/image-2.webp 400w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/8d2ea/image-2.webp 800w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/68fc1/image-2.webp 1200w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/4b075/image-2.webp 1280w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/e3c98eb0b1aa9dec8f082f1f187eddc3/36ca5/image-2.png 200w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/a3397/image-2.png 400w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/a331c/image-2.png 800w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/8537d/image-2.png 1200w,
/static/e3c98eb0b1aa9dec8f082f1f187eddc3/26c69/image-2.png 1280w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/e3c98eb0b1aa9dec8f082f1f187eddc3/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;Michael Drogalis&lt;/p&gt;
&lt;p&gt;Weekly experience report as I bootstrap &lt;a href=&quot;http://shadowtraffic.io&quot;&gt;http://shadowtraffic.io&lt;/a&gt; from day 1.&lt;/p&gt;
&lt;p&gt;](&lt;a href=&quot;https://michaeldrogalis.substack.com/?utm_source=substack&amp;#x26;utm_campaign=publication_embed&amp;#x26;utm_medium=web&quot;&gt;https://michaeldrogalis.substack.com/?utm_source=substack&amp;#x26;utm_campaign=publication_embed&amp;#x26;utm_medium=web&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;He’s also doing the same on LinkedIn: &lt;a href=&quot;https://www.linkedin.com/in/michael-drogalis/&quot;&gt;https://www.linkedin.com/in/michael-drogalis/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And, of course, check &lt;strong&gt;&lt;a href=&quot;https://shadowtraffic.io/&quot;&gt;ShadowTraffic&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Plus, our past webinars&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;webinars&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#webinars&quot; aria-label=&quot;webinars permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Webinars:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing&quot;&gt;#1 - From CRUD to Event Sourcing&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short&quot;&gt;#2 - Keep your streams short! Or how to model Event-Sourced systems efficiently&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-3-implementing-distributed&quot;&gt;#3 - Implementing Distributed Processes&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice&quot;&gt;#4 - From CRUD to CQRS in practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100&quot;&gt;#5 - Architecture Weekly 100 Edition - Live Q&amp;#x26;A&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev&quot;&gt;#6 - Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven&quot;&gt;#7 - Design and test Event-Driven projections and read models&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates&quot;&gt;#8 - Slim down your aggregates!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization&quot;&gt;#9 - Radek Maziarka - Modularization with Event Storming Process Level&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers&quot;&gt;#10 - PostgreSQL Superpowers in Practice&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski&quot;&gt;#11 - Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify&quot;&gt;#12 - Jeremy D. Miller: Simplify your architecture with Wolverine&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic&quot;&gt;#13 - Yves Goeleven - The Fantastic 9&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-14-mateusz-jendza-why-verified&quot;&gt;#14 - Mateusz Jendza: Why Verified Credentials is the Future of Digital Identity!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-15-mario-bittencourt-leveraging&quot;&gt;#15 - Mário Bittencourt: Leveraging BPMN for Seamless Team Collaboration in Software Development&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-1-sagas-hector-garcia&quot;&gt;#16 - Papers We Love #1 - Sagas (Hector Garcia-Molina, Kenneth Salem)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-16-simple-patterns-for-events&quot;&gt;#17 - Simple patterns for events schema versioning&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-17-andrea-magnorsky-introducing&quot;&gt;#18 - Andrea Magnorsky: Introducing Bytesize Architecture Sessions!&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/webinar-18-laila-bougria-debug-your&quot;&gt;#19 - Laïla Bougriâ: Debug your thinking&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;#20 - Papers We Love #2 - How do committees invent? (Melvin E. Conway)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;#21 - Michael Drogalis: Building the product on your own terms&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title><![CDATA[Architecture Weekly #192 - Talk is cheap, show me the numbers! Benchmarking and beyond!]]></title><description><![CDATA[Boy, it’s the fourth episode of the refreshed format, time flies! We started by learning the importance of connection pooling, discussing…]]></description><link>https://event-driven.io/en/talk-is-cheap-show-me-the-numbers/</link><guid isPermaLink="false">https://event-driven.io/en/talk-is-cheap-show-me-the-numbers/</guid><content:encoded>&lt;p&gt;&lt;strong&gt;Boy, it’s the fourth episode of the refreshed format, time flies!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We started by &lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;learning the importance of connection pooling&lt;/a&gt;, &lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;discussing how to implement it&lt;/a&gt;, and then plot twisting to &lt;a href=&quot;/en/architecture-weekly-191-is-it-production/&quot;&gt;explain why what we learned so far is not yet production-ready&lt;/a&gt; and what does it actually mean to be production-ready!&lt;/p&gt;
&lt;p&gt;Lots of talking, especially considering that those topics were more to trigger discussions on different deployment strategies (e.g., serverless or not), how and when to scale, and why queuing is essential. Why so much talking? Because it’s cheap! It’s cheap to discuss and analyse tradeoffs before we make final decisions. Yet, we can’t fall into analysis paralysis constantly going back and forth; there’s a time when we’ll hear:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Talk is cheap; show me the numbers!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If we don’t, we’ll see them. And I intend to show you the real numbers today.&lt;/p&gt;
&lt;p&gt;But before I do, I need to tell you a secret. I’m writing those articles not only for you but also for me, maybe even primarily for me, as I’m writing them not to forget. Forget about what? About what I’ve learned. But I hope you’ll forgive me for this selfishness, as there’s an additional benefit for you: they’re real. They’re showing my journey and are the outcome of my real architecture considerations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Currently, I’m building two Open Source tools: &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt; and &lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt;.&lt;/strong&gt; Both of them are Node.js storage tools. Emmett intends to bring Event Sourcing to the JS/TS community and their applications back to the future. Pongo is like a Mongo, but it is on PostgreSQL. So joining Mongo experience with PostgreSQL consistency and capabilities. So far, the feedback is positive. But it’s an intriguing journey touching multiple aspects. Not only technical but also product-building, as I want to make it sustainable for me and useful for users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The hidden trap of building something useful is that people will use it, which can cause some trouble.&lt;/strong&gt; I’m a big fan of the slogan “Make It Work, Make It Right, Make It Fast”. Why? From the C2 Wiki:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If it doesn’t work right, in what sense does it work at all?&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Here’s my interpretation. First crank out code that handles one common case (MakeItWork). Then fix all of the special cases, error handling, etc. so all tests pass (MakeItRight).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Another interpretation, “Make it right” means make the code more clear, i.e., refactor. “Make it work” is the part about getting the code to operate correctly. A rephrase might be, “Make it work correctly, make the source code clear, make it run quickly.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, I think what’s there in Emmett in Pongo works. Based on the feedback, it sounds kind of right. Is it fast? Yes and no.&lt;/p&gt;
&lt;h2 id=&quot;the-issue&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-issue&quot; aria-label=&quot;the issue permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The issue&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;That’s how we got to the troubles users can cause. They might want to use your tool, deploy it, and verify whether it works and how fast it works. And they may find out that it doesn’t work as they expected.&lt;/p&gt;
&lt;p&gt;Jokes aside, having early adopters and getting a feedback loop is crucial. I’m lucky to have people like Fernando who have contacted me and said they observed something weird.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;He said that locally, hosting PostgreSQL on a Docker container, the application was running instantly and super fast. Still, he never got a request processing time below one second when deployed to the end environment.&lt;/strong&gt; That could not have been an issue a few years back, but it definitely sounds unacceptable nowadays. I told you that I’m lucky, and I do.&lt;/p&gt;
&lt;p&gt;Fernando is building a serverless-first product, which is a common case for startups or those in the early phases of development. With this model, you pay only for what you use. That means each request will (or can) spin up a new stateless environment. We cannot have a shared state between the calls, e.g., we cannot share a connection pool.&lt;/p&gt;
&lt;p&gt;That’s a challenge but also a valid use case that I want to support. One of the reasons why I selected the Node.js environment to build my tools is that it helps me deliver faster and more sustainably.&lt;/p&gt;
&lt;p&gt;We discussed already in &lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;Mastering Database Connection Pooling&lt;/a&gt; that there are “Connection Pools as a Service” like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://aws.amazon.com/rds/proxy/&quot;&gt;AWS RDS Proxy&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-pgbouncer&quot;&gt;Azure Database for MySQL/PostgreSQL Flexible Server Proxy&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.cloudflare.com/hyperdrive/configuration/how-hyperdrive-works/&quot;&gt;Cloudflare Hyperdrive&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://supabase.com/blog/supavisor-postgres-connection-pooler&quot;&gt;Supabase Supavisor&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They can keep the shared connection, but they can cause issues if you’re using an application-level proxy like &lt;a href=&quot;https://node-postgres.com/apis/pool&quot;&gt;pg pool&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I used connection pooling as a default design choice for both Emmett and Pongo. They’re sharing a common dependency &lt;a href=&quot;https://github.com/event-driven-io/Pongo/releases/tag/0.10.0&quot;&gt;called Dumbo&lt;/a&gt;, which is responsible for connection management and running database queries.&lt;/p&gt;
&lt;p&gt;Nevertheless, I added the option to turn off pooling to support the “Connection Pool as a Service” scenario. Knowing his use case, I also recommended that option earlier to Fernando, and that’s also one of the reasons for the performance issue. We’re getting there.&lt;/p&gt;
&lt;h2 id=&quot;the-initial-analysis&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-initial-analysis&quot; aria-label=&quot;the initial analysis permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Initial Analysis&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Emmett and Pongo are logically simple tools; they’re:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;taking the request (reading events, appending new, filtering documents, updating them, etc.),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;translating that into the SQL,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;getting database connection (and transaction if it’s a write request),&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;executing SQL query,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;committing transaction, if it was open, and closing connection.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I’m outsourcing the database stuff to the PostgreSQL driver, so I won’t do sneaky things that could complicate this simple process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why the first step was to cut off external and the most obvious issues.&lt;/strong&gt; What could be they?&lt;/p&gt;
&lt;p&gt;Deployment specifics? Serverless, in general, is vulnerable to the cold start issue. Cold start happens when the function is run after not being called for some time. Then, the whole environment needs to be started. The next request can reuse this environment without additional delay. After some time of inactivity, the environment will be again &lt;em&gt;“put to sleep”&lt;/em&gt; so you don’t use resources and don’t pay for them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, that wasn’t an issue here, as we verified that it’s happening constantly, not only on the first request.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Some deployments are different. For example, &lt;a href=&quot;https://developers.cloudflare.com/workers/&quot;&gt;Cloudflare Workers&lt;/a&gt; use custom, slimmed Node.js; even AWS introduced recently &lt;a href=&quot;https://github.com/awslabs/llrt&quot;&gt;their own Node.js runtime&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But it wasn’t that; it also happened when running locally against the actual database.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The distance between the database and application hosting? If they were hosting in different regions (e.g. database in the USA and application in Europe), that could add significant enough latency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Wasn’t that? Both deployments were in a similar region.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So maybe that’s the issue with the database? It could have been underprovisioned (so had a shortage of needed resources). If you’re using innovative hostings like &lt;a href=&quot;https://neon.tech/&quot;&gt;NeonDB&lt;/a&gt; and &lt;a href=&quot;https://supabase.com/&quot;&gt;Supabase&lt;/a&gt;, which are adding their sprinkles to make PostgreSQL serverless and cheap, then maybe those sprinkles are the reason.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nah, running against the regular PostgreSQL hosted on &lt;a href=&quot;https://azure.microsoft.com/en-us/products/postgresql&quot;&gt;Azure RDS&lt;/a&gt;. Gave the same subpar results.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Then maybe something in the application is causing delays. Maybe an unusually big payload is causing delays. That could increase the latency between the application and the database and add to the serialisation time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nope, the payloads were even smaller than they typically are.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So we’re back to the initial point, and the thesis is that it can be a bug in Emmett or Pongo.&lt;/p&gt;
&lt;h2 id=&quot;show-me-the-numbers&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#show-me-the-numbers&quot; aria-label=&quot;show me the numbers permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Show me the numbers!&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If that sounds like guessing, then you’re right; calling that analysis is a bit of an exaggeration. But it’s a necessary step to avoid dumb mistakes and understand the deployment specifics, data usage, and scenarios. This is needed to reproduce that and set benchmarks. Yes, benchmarks, because we finally got to the numbers.&lt;/p&gt;
&lt;p&gt;I reached the moment when I knew the primary issue. I wanted to know more about the reasons, so I had to choose tooling. I was correct, I was right, but I wasn’t fast, and to fix that, benchmarks were needed.&lt;/p&gt;
&lt;p&gt;I chose &lt;a href=&quot;https://benchmarkjs.com/&quot;&gt;Benchmark.js&lt;/a&gt; as it’s the most popular microbenchmark tool in Node.js. I didn’t need load tests yet, as I wanted to pinpoint the specific issue to make the initial improvement. Each development has its own benchmarking tools, I’ll show you the syntax, but it’s not as critical as understanding what we’re doing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The first step was reproducing the intended usage and confirming that we were getting similar numbers.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;I’ve set up a NeonDB free-tier database in a region similar to my local computer. This database is underprovisioned for bigger usage, and that’s fine, as such a setup should make the observed issue even bigger.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I’ve set up the test with the event store connected to that database (of course, making that configurable). And doing basic operations like appending and inserting. It looked like this:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b3f5ff604ec4fb5177934a9230e2f384/88420/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 118.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAYAAAD6S912AAAACXBIWXMAAAsTAAALEwEAmpwYAAADeklEQVR42o1VW47bNhT1YqbWi6RE8SWRlF/IR4GuKUjRhQQJ+hEg+2ghtJgFJaBxe0i7sSeIg/k4uBRneIfncTmbX7V9F0/q85PUH6ta/Vm1F9Td69Ew/RH1c9OpPzaz8usUF+rUlJiciI0TcaAV7oL+NbA4O+f6vGHC/6XkiQY9fZFuSgKNezWnRuiCmumEG6P+BFx9aYUhrP/ZaHlco/uNnIvpdFhoCTvS6kB2AnaRlA/U64mackA/gEqtsFR1+nnTD2H17g0NuNWgHGWI0VEHulumqBaamt5Q9bDZdw273qzLsqdljukwLxRdpGgDzcaTGibSGf1MPbR6ZcNp1epIQrokekuM64K209SxG9p8sFM3PGrI+bxatSejp6SmCN2OpOOB2jFQqyI10pdavstegATuccOKqVWDop5CEqArQHuIexr8Qn3GHEsdAuAj4uWp4tD0esttl3HXkOGGxsBRNyftPM3IpA2RBruQ9ke4fKARjqsZmPY02kij2VGPW7PBgZkjObqUM1satsysVi/krU/G70iGI3E04G5PfMbaHUjgm9kdcdDtzUxdDjNu1OCmDHFqubk1xNXXFk6ybIpEXIohiqp2/AFUQc1upqBJpn+nYW7ILbXSpwqi56ZisBdt2A1Zs5zFe/zQFMandRyhjXZJYyI4Gg6g9H8GDZDXEhixL68/y7+TKV+bvszh4KCVXdIAA1BpxKBPgIdmYfRksWY4wL/DreH96MmwKg3RB5u6wWNu8fLgBmX07mm+ljLnfjXqhMdhSh45M4hONonrADdtdhBGmXt6Px+9HBunT8jYLi3LQns0lQqza2eS0NSieabcjzPVD5veNcQGTEFIx5hkj8MCBmDcaqzrrBPEz7PMecnbt1muXuLaUOXR09DwhPQfk82vTAikdgeMH4yKbzB2kANGBYPRQ8A7BBwRQ8DhNOQRqAIJ4XnmhQHl3q4cFJG1lPNWi6yXooaNL8woBuUb8ou7+cEtKHs65enB/5YSm7853O2E+cp7c265PkMrVHNmVzTCnpthOoPWmeW1MGf8YXxf0HD7NVOGFP+WF1ubE9wNyeFlGfCacJVrIIvJMVgLi7mejiTtAY/BAop5pgMoBzwcMa9BOafCPW+2rXq7bcdPv9Tj+6dGfQC1D1UnS326ompkwRZ4ymvsbZt7jO8x65+w//t/plPZDjjkKt0AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/b3f5ff604ec4fb5177934a9230e2f384/507e6/image-2.webp 200w,
/static/b3f5ff604ec4fb5177934a9230e2f384/28a80/image-2.webp 400w,
/static/b3f5ff604ec4fb5177934a9230e2f384/8d2ea/image-2.webp 800w,
/static/b3f5ff604ec4fb5177934a9230e2f384/68fc1/image-2.webp 1200w,
/static/b3f5ff604ec4fb5177934a9230e2f384/7d801/image-2.webp 1468w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/b3f5ff604ec4fb5177934a9230e2f384/36ca5/image-2.png 200w,
/static/b3f5ff604ec4fb5177934a9230e2f384/a3397/image-2.png 400w,
/static/b3f5ff604ec4fb5177934a9230e2f384/a331c/image-2.png 800w,
/static/b3f5ff604ec4fb5177934a9230e2f384/8537d/image-2.png 1200w,
/static/b3f5ff604ec4fb5177934a9230e2f384/88420/image-2.png 1468w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/b3f5ff604ec4fb5177934a9230e2f384/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Having that, I could set the benchmark code:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/66ae462edd0ca64bb0652246ac5002b7/88420/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 155.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAfCAYAAADnTu3OAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEB0lEQVR42o1W224jRRDNx7COZ/oyfe+5eTxJNiDxQ6z4INiVn0CgvCEQH8Uij1ScajtL5NjOPpR6bl116tSp6rl5b/KH7Rz+eBf9r6va/7YSMKy3bCJ8la2l/+VW+D+x74ebaPtd108kXb+oZiDtOjKhpcZ3pGxHtc6wRHWTL1ql0165Adfp402t8qfkv6Uc7v7NeVpi3C5zD2uHJcRxEWlapGkXoLhmn0WT6Fb6n29W0u2c25IzI78gwKbbOsA8VSJwdKpUpLUMFw3p7gWQwuFHOAw73XTk/bgopCl9IuEOVnNUUTa8Mg5+1iEe7GqFzXGz2OGB2m6ikDbUgJPGt2RcS9JkUq4vnCrTHrlNVxwe0lr6+EhTuKdtfqAhwXk7kUOBtOUijaT9AGc9rgdiB1ccolpwaOC40ZFqIKjcAcXqTLpvpwyHChxKpFj4s5GEiaUYlzkM1ziEA8jDbR6JNZmQqkFaBgGsQzBwqMEp8yiBXmN9g0PwgZSTv6dst9T7LXQ5k4twCsRciFOHb3Mo4+IhH4cX0mNDGKDBTN9U0GZl6R1sVbsvdj1lbNSBddhSbYBI96Q1ZAM0xueCyMZNWU8lc7koaK8WyJowUghI2Y5IG44S+ru7L7Lh73jjaeecdcjDQUNzKqLS0J0x4KwB0jIwwGXYFP0VHsHpVYfcr8r0S9t+R2l+pDTONA8zDdw58/eo+h3l/oFcOxdx11+DEA+XHmn2SLnpZjJA6jNaLSFt8Miir7lDGN1Rh6zRtbgkGxkWhU0mdnA4Qh7gyyYg4j7O5ECDMl3pIoH0FTqpvPNl73mHzvLUwYd5A57QMTBpD6tGMNG0VGEjoxSGA+ayrtUFhxoPLRBqpMnRpTsgKFOHHaEgFVB6x8hxjco30CuC7jnYK4cWkS1Sk0XU8WAoABcBwuduorXmyRxJ6gAZodd59Om45w7C8xMOgaKBdEwLUSeIGkVhKVnQUDhEwSSMi+OZa4hdpglou30DzSLgS4d+2eDDAYTL9p4MHMZpINv25PqeUobFEfczKQhdDu/JjVsKCL6WcV+U8hIhD9gA2I4nctqW0wxkl5TZxDHl6nDKleOh1qHQcnZiw0rKa3BSGd4QC0cVNpXZiPuKhcwafLYibH++U5qmWwZ/h7Qm6rstumMgN92R6zZkka5JQxldvJEr+rzyefQaYXmZl+NRQAIficJrLFY64thmzxQ82+H9iUPJEXk4oGoCCEtVUbW3zuOzvQzuPrHycT5/5hcHc/vVi+v/n1+0f+qiz/DTjW7aXYZUku0XjzHFfdrAuOIanSCBuHBV/nHOW6Xwb2N5CuHfBr8bP6JP/4L9LlV6giyeAP9J4Voc71HdJ1T1olX4Y8P697r2H/4DtYyqDGSDkokAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/66ae462edd0ca64bb0652246ac5002b7/507e6/image-3.webp 200w,
/static/66ae462edd0ca64bb0652246ac5002b7/28a80/image-3.webp 400w,
/static/66ae462edd0ca64bb0652246ac5002b7/8d2ea/image-3.webp 800w,
/static/66ae462edd0ca64bb0652246ac5002b7/68fc1/image-3.webp 1200w,
/static/66ae462edd0ca64bb0652246ac5002b7/7d801/image-3.webp 1468w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/66ae462edd0ca64bb0652246ac5002b7/36ca5/image-3.png 200w,
/static/66ae462edd0ca64bb0652246ac5002b7/a3397/image-3.png 400w,
/static/66ae462edd0ca64bb0652246ac5002b7/a331c/image-3.png 800w,
/static/66ae462edd0ca64bb0652246ac5002b7/8537d/image-3.png 1200w,
/static/66ae462edd0ca64bb0652246ac5002b7/88420/image-3.png 1468w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/66ae462edd0ca64bb0652246ac5002b7/a331c/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As you see, it’s a simple scenario; nothing fancy between telling the benchmarking engine what to do multiple times and gathering results. I already thought those delays may be related to pooled vs non-pooled usage, so I included a basic feature toggle (passed as an environment variable).&lt;/p&gt;
&lt;p&gt;Sidenote: If you think you don’t need a benchmarking tool, you can get those times on your own with simple timers, then check this talk. Andrey Akinshin nicely explains how much statistical knowledge is incorporated into building such tools to give you the best results. &lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/5S8c3j2HFpY?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;Nevertheless, even such simple benchmarks &lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/108&quot;&gt;gave me enough food for thought&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;non-pooled&lt;/strong&gt; connection, I got:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Appending events x 1.98 ops/sec ±28.76% (15 runs sampled)
Reading events x 3.05 ops/sec ±12.89% (19 runs sampled)

Benchmark: Appending events
  Operations per second: 1.98 ops/sec
  Mean execution time: 506.14 ms
  Standard deviation: 262.83 ms
  Margin of error: ±28.76%
  Sample size: 15 runs

Benchmark: Reading events
  Operations per second: 3.05 ops/sec
  Mean execution time: 327.42 ms
  Standard deviation: 87.54 ms
  Margin of error: ±12.89%
  Sample size: 19 runs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And for &lt;strong&gt;Pooled connection,&lt;/strong&gt; I got:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Appending events x 7.52 ops/sec ±29.75% (46 runs sampled)
Reading events x 25.10 ops/sec ±3.40% (61 runs sampled)

Benchmark: Appending events
  Operations per second: 7.52 ops/sec
  Mean execution time: 132.99 ms
  Standard deviation: 136.92 ms
  Margin of error: ±29.75%
  Sample size: 46 runs

Benchmark: Reading events
  Operations per second: 25.10 ops/sec
  Mean execution time: 39.84 ms
  Standard deviation: 5.40 ms
  Margin of error: ±3.40%
  Sample size: 61 runs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Exercise for you:&lt;/strong&gt; Look at them and think about what’s odd here. Then, move on to the next paragraph.&lt;/p&gt;
&lt;h2 id=&quot;analysing-benchmarks&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#analysing-benchmarks&quot; aria-label=&quot;analysing benchmarks permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Analysing benchmarks&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;I got a better but similar result for a non-pooled connection (the one Fernando used). If you sum up mean execution and standard deviation, then the time could be similar to the one he observed: almost 1 second.&lt;/p&gt;
&lt;p&gt;Appending was slower than reads, but that’s expected.&lt;/p&gt;
&lt;p&gt;For the pooled connection, I got much better results—not great, not terrible. If:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I had a bit faster DB than the free tier and…&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I wasn’t running benchmarks from my local computer, but from the same network as the database. They should be better and better enough for most systems.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We know pooling should give better performance, but why three times better? That’s odd. Also, why is this standard deviation so big? It’s almost the same as the mean execution time. &lt;strong&gt;That suggests that there’s something wrong with the test setup.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To understand why that was happening, it’s necessary to know how appends and reads work internally.&lt;/strong&gt; Both Emmet and Pongo try to reduce the boilerplate.&lt;/p&gt;
&lt;p&gt;One of the aids is generating the database schema for you. On the first call, it’ll create the schema. Then it won’t repeat that. As the event store should be used as a singleton, it’ll only happen for the first request, and then subsequent operations won’t have this penalty. That’s good enough for local development and even stateful deployments, but… It’s actually not great if you’re doing serverless. Why? Because potentially, each call is the first one, as we don’t have a shared state.&lt;/p&gt;
&lt;p&gt;To validate that, &lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/109&quot;&gt;I updated the benchmark setup&lt;/a&gt;, doing the first call before the benchmark run:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5279b57655301d40d5cde43ba05d3cca/33c03/image-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 77.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAAsTAAALEwEAmpwYAAACAElEQVR42p2US5LTMBCGfRni2NbDtmRZku3EJKFmwwU4AOQeU2ymKFZwV4ZSqn5acorMIpMJLL7qtqP+3a8o22nzXc36cdXqr+uSYIQ4w+6j4PpxzdS3NW9V9mm7gftowqqzKFiPQhC8x5ovfklU0l7h8p7OnHg7oJT2mIlmB8mnX33vQm8XvB/CMI7Jb7QNOTeBMknk0fIukVeK3iWeC2GwLrvPWdt6OOWD1x7CjJB+hpw+oJkfINx7iPEA5nZg/QwefXu29FvVTaiUQ927k2gdcqaOWd2MYJQBFw5cDFSCg2gNYYkenGxVW1B2C7y7gj7FFiTBvjvAqodgzR4bv4fSE6Zhh/044zBuMLsJjdmiMhNin/4Kv6RSp9jrJFiSMhc+MGkga41KaHBO2TLKkr4uuUE8U9TLIKhvtwVFPUDULjg1oG8XrPIY9Ui+R0d9Yo1DyTqavkmiRSzz1QzJEdIHrWa0FGhIWJPtqMwolFajvqwKI/+mYAyKgrWkCdPoFQW0FCjOgbHEpUz1wr9VcjPQQpu4X1jR4XdEtKuyjQfPQeqMfnsosplg212weksbb9I/I9pYXnqWl+e7BFPJdR+aGEwlLqIXoX8WrGnvqFfPVOYpfwMq+zV+x4/Sgn/JBE2V1MP1C+A+LpeDOWaU5k9K+yldP/+Nfsor/aMslfkDyQ3YFZ0YjHMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5279b57655301d40d5cde43ba05d3cca/507e6/image-4.webp 200w,
/static/5279b57655301d40d5cde43ba05d3cca/28a80/image-4.webp 400w,
/static/5279b57655301d40d5cde43ba05d3cca/8d2ea/image-4.webp 800w,
/static/5279b57655301d40d5cde43ba05d3cca/5ad0e/image-4.webp 1140w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5279b57655301d40d5cde43ba05d3cca/36ca5/image-4.png 200w,
/static/5279b57655301d40d5cde43ba05d3cca/a3397/image-4.png 400w,
/static/5279b57655301d40d5cde43ba05d3cca/a331c/image-4.png 800w,
/static/5279b57655301d40d5cde43ba05d3cca/33c03/image-4.png 1140w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5279b57655301d40d5cde43ba05d3cca/a331c/image-4.png&quot;
            alt=&quot;image 4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Then I reran benchmarks with schema generated upfront and the results look as follows for &lt;strong&gt;non-pooled&lt;/strong&gt; connection:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Appending events x 2.40 ops/sec ±13.17% (16 runs sampled)
Reading events x 2.90 ops/sec ±18.93% (18 runs sampled)

Benchmark: Appending events
  Operations per second: 2.40 ops/sec
  Mean execution time: 416.37 ms
  Standard deviation: 102.96 ms
  Margin of error: ±13.17%
  Sample size: 16 runs

Benchmark: Reading events
  Operations per second: 2.90 ops/sec
  Mean execution time: 345.36 ms
  Standard deviation: 131.47 ms
  Margin of error: ±18.93%
  Sample size: 18 runs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And for &lt;strong&gt;pooled:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Appending events x 9.17 ops/sec ±0.72% (47 runs sampled)
Reading events x 27.58 ops/sec ±0.34% (65 runs sampled)

Benchmark: Appending events
  Operations per second: 9.17 ops/sec
  Mean execution time: 109.05 ms
  Standard deviation: 2.76 ms
  Margin of error: ±0.72%
  Sample size: 47 runs

Benchmark: Reading events
  Operations per second: 27.58 ops/sec
  Mean execution time: 36.26 ms
  Standard deviation: 0.51 ms
  Margin of error: ±0.34%
  Sample size: 65 runs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That almost removed the standard deviation error in pooled benchmarks, reducing it and significantly improving the meantime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now, the question is, why is schema creation so costly?&lt;/strong&gt; Am I having some wild setup? I don’t. Those are just a few tables and CREATE IF NOT EXISTS statements. That can lead to the conclusion that it’s not an issue with the schema creation but with opening the new connection.&lt;/p&gt;
&lt;p&gt;Benchmark times can also suggest that. If we’re doing append as the first request then we’re making two calls: 1st with the database schema setup, 2nd with append. If we’re running the non-pooled setup, it’ll open two connections for non-pooled, and that will be one.&lt;/p&gt;
&lt;p&gt;For the setup without upfront schema creation, I got 0.5 seconds of the mean execution time. Fernando had over 1 second. Why? Because he was initially benchmarking the command handling process, not just appending it. Command handling consists of reading events and appending events. So if the issue is indeed with the opening connection time, it could be doubled. But to prove that, we need to have something more…&lt;/p&gt;
&lt;h2 id=&quot;benchmark-baseline&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#benchmark-baseline&quot; aria-label=&quot;benchmark baseline permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Benchmark baseline&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;A baseline is something we compare to. A similar scenario, but trimmed with all the fluff we have in our implementation. We do that to compare to the “solution without our code”. So, in other words, we’re removing the latency that our mishaps could cause. In our case, this could be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;running just regular INSERT and SELECT from Dumbo and raw PostgreSQL driver,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;opening and closing connections both with Dumbo and raw PostgreSQL driver.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The benchmark &lt;a href=&quot;https://github.com/event-driven-io/Pongo/pull/68&quot;&gt;could look as follows&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d47bfa07557135420dfdc0d469311140/47e9c/image-5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 370%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAABKCAYAAAC7DXSKAAAACXBIWXMAAAsTAAALEwEAmpwYAAAI6klEQVR42p1Y227jyBGdf8mMJFJk3y+8iZbl8cwgCJBfyMP+1GIfBgKSl+QHd9ECKqealCXLslbrhxIpmypWV506dbo/+dr/azvY/xSd/XW5tL8tS1jBV/dbUd1ptf91tXb/XsLXpyi7/T+edyQ3bRK6pdq2JGC1aWgt411WinCoTUeljPtPqyr8lGpL6zr+XrlwqDT+6eKhtuGAaA8L2HJtD6sbhv//UdaBcP/zU1XHfRd+kHdDamJPbFpvSOqBZGhIRUTrGlpWllaVu2r8wlJEPOP2cNjut+0/KfptGvoRDh+oGTZkXEfSRLIBKdAdCdljSYEK4W87LBGhszuqVJNciGRsoOAbsi5SrQIpHZCOQLVoqagdFexkbSd7z2Gwj9TYLnks13UP5Mcd2WFHhR2ocBsq+Qor53u+rv0G0YbJ6bnDRWn22rfUdH1SzUACpoYHMuMWTrH0YYRtSHfTvR1HUi0cAxGTM0RYnkdY+b0yIwnTJOMjVdKTRq4UrLQdVYikboACN1AVRtgUXYnvpelprZEaIAIpg+NclLi3ZkfOwiHypoCroFAIPFDASsaXRUFQGL4vENl03+b7grGo/GEtm1OE2j+SabdJRCwvPJBDpVe1PyW/NKf7M1se7XzJ/CG7Ha3758RvXuKNK0T2Hub+FIfow71ALvDH9CqCjzqccogl2zYNw0AWGKxVT4G7hQvBRQFMKqSiirOhOPWxytccGjgUuknazQ3PyeYc1nNXHO/P7b0I+aOxI41xmxzAnCNCTmsuzNqd7N4l8wfoh5rQpdAOmQgEIpX5OpEDX2v0dVH7ex2issIl/zBQuwNBwIbvjxTwXYEcSoCdbVW7+x1WKiT+cWUCST9Fh799YMnoZWZbLCfNICWQKi0LfvADsGG2sWAbbWKKyJcDUfjI1mQIKTtVXktARzSZFIo63AA2PozdosrP6bEdqUNhJOaJt2Ac9GsEQVjNcwYOgUtezZ/k0OzXKlKjN8mABGr0swSIJYC9rjwZRCNQ3UVuSUSHH37h1NzKodA9Df5H0nAoscRKNyRlRzWcLBERd4XBvQK8DBszjgjvR1hJEKz7kQLYuEd1pQeR9t+Qw02mLHbItCZgMnMlclmH2xFG+5wMluTxsESX8NK5xYqZohb8Q9hitptsw4n+XOj0GXD5wlaYbIsMoQk+E/eZq2x0hRy2gE2bAsOFYQOAh4jq4t5wCwLsPEaVHMDOTbabsPH2CYN+TF3fUdv1GYPBYz47NmARc0TEHfoakuV82l1zyFPPmAeK7luSyJ/F2zW3ogEXAnvrOfnLuY+Lu1oP1N9ylbF0hoREBMyJax5QF9Hc1csS0TjzlLTqyAODFvDgQjE3ctv95ZlSIZq/FSp95upyZc8n2jUnF6QBIXUxU0AOznWpaabK8nKriYEuzE0mzu7rKUJeyQsfOrNBh/Sp9R1pDzWQFcJIJczBeeC8AkrMkzVa03bdxOr4rhsEIMNBc/WPDrkoeEOqZkXKrVayMp0xx3+7pKxpeJ2WnJ85Ljm6Jyy1T6HfUOgGqAeOAMKJtQ2WV7HOkeFq/q6OAId8bTD1mnZHvn0ktf1O+uEbhc0jdU/P1OBFrL1LFU85FP5laL1xuALWAJXUY+naod3QLRIFkmi/OvQkUCiBfHEepykYs63lFX2Y2QZSBO2UljOjHJu/+IgU4Q9OPBKbsjBnGICpF8hVHlZ36JyLIdXum4B8ua+AzTPY5pF6KNYWsyVi2eKO9rtoPTdNPbVJHq1nTD8NdjEl/6gTb0X6ZgQoDCSlBuwCAFxIXK0gOsHeEaaZJMDgDKO7I1SIDJMtlTPOigq4Qx556hUVRxnydWLuO4qiEIltnxKre9syNLjFQoaKmGUJ76qOrcc5Xb63T+GP3Ph+A429BUn0xPO5wkanApB5rPKOikcrdw5bPU9CVv7826tiCW9ND/HvNLgdbUH12+aZRmwnnrA/2aLiHYii6Z6yqmXcSozc2w4ryDksnQd53qqy7EDnTBsbLG3GJHMlxkaegC/LvuwUfhOqmHJ/qqlPS+FnXYhxoCbj79dE59tO4aHU7JLCbtR3gAr6teS82clY1WIPDXIIxyXeLgrLCiwxufgdY/MROgebSQ0FizyNoLO+wWiFPInQ3g5sxJsjA2xKFO8lh/Iih5AciQvSYGC1/OMGTlGICEnnUVGLKvNErOYdwqulv4mQWVrFJJA3lm4r3rcB7Cs8xEX4MqvaxTstuLxWlNqPqbK4YhMufEAkLuttLgpf69lK8UKqLzR3NYcVOkUPT2AZjIEG213WiiBbje4wfPYAomXjvDGcGNjHmX0Vh0ywwX5FvqBnsH/uoCIitmS8y9dcaThRKJI8s3rO59WNDzRM2vgttfihRTE0qmlRHJ52Jzn32m4ydqXaNKHfZbY+jgMeA9NA9y/7v6nC/oZDlrvd12Q3W2pGjNGmfdmSYdsxOf8rEXJRCtsnESBJIOEslIQSyBPTWj5yaXKRFHKq3F05zBvDtI0/aESnPIBtNt23PKMFzm0EWi5DawY20pO/l7eqzEXhbYPEG3mgL/X0A9bYGdiFhu7WmWmObHMD2MhX2KQC0RRglKoGOwuIJmzFmGAFXsDTjzHIXMj3r3TjtdZTuk0NzrsE1JdDLnv7kHEYWTUA6Lp/AsgnUp2INZyKcxnhGhEqjABteXagC3DVUP0GZw9a8t4PUw+TscYG6Rgp5zJfWSS8YZss3dqpl9kpfoSxCifYgHPF4ViALKTuc5Xx8lxlvrLjVw6LLJY8Wm6XOvedRjz4CK7T4D7d7+ZqhkkvZv3Y5KlXnk43385l7hQsJ3Exaq4ySw81bRCL+fvJJvF5/H4Vh/xWEcYk8jkNosModX5ilQ+dLPGh2QJss2IoyDwBARk8fBzopbnf4RqHugHV8qr53cnIx3YHzJeD1O3BCj7Gaw+l3xzw4ny4+479wWlY5ENd2e4d8AZRlLTpcs9W3KuGD3dZJfT5qCoT6lHEXxgKhFPlfj52rv0vi7X5H3vHPnjPuGTj+/PvvJz3DMv+ib3gfxe1/eX/VYfKBe+I/V8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d47bfa07557135420dfdc0d469311140/507e6/image-5.webp 200w,
/static/d47bfa07557135420dfdc0d469311140/28a80/image-5.webp 400w,
/static/d47bfa07557135420dfdc0d469311140/8d2ea/image-5.webp 800w,
/static/d47bfa07557135420dfdc0d469311140/68fc1/image-5.webp 1200w,
/static/d47bfa07557135420dfdc0d469311140/a0d74/image-5.webp 1296w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d47bfa07557135420dfdc0d469311140/36ca5/image-5.png 200w,
/static/d47bfa07557135420dfdc0d469311140/a3397/image-5.png 400w,
/static/d47bfa07557135420dfdc0d469311140/a331c/image-5.png 800w,
/static/d47bfa07557135420dfdc0d469311140/8537d/image-5.png 1200w,
/static/d47bfa07557135420dfdc0d469311140/47e9c/image-5.png 1296w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d47bfa07557135420dfdc0d469311140/a331c/image-5.png&quot;
            alt=&quot;image 5&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;After running it, I got the following results for &lt;strong&gt;non-pooled&lt;/strong&gt; connection:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Opening and closing raw connection x 3.46 ops/sec ±14.99% (21 runs sampled)
Opening and closing connection x 3.19 ops/sec ±17.17% (21 runs sampled)
INSERTING records in transaction x 2.50 ops/sec ±13.55% (16 runs sampled)
READING records x 2.75 ops/sec ±18.46% (18 runs sampled)

Benchmark: Opening and closing raw connection
  Operations per second: 3.46 ops/sec
  Mean execution time: 288.62 ms
  Standard deviation: 95.06 ms
  Margin of error: ±14.99%
  Sample size: 21 runs

Benchmark: Opening and closing connection
  Operations per second: 3.19 ops/sec
  Mean execution time: 313.72 ms
  Standard deviation: 118.31 ms
  Margin of error: ±17.17%
  Sample size: 21 runs

Benchmark: INSERTING records in transaction
  Operations per second: 2.50 ops/sec
  Mean execution time: 400.40 ms
  Standard deviation: 101.80 ms
  Margin of error: ±13.55%
  Sample size: 16 runs

Benchmark: READING records
  Operations per second: 2.75 ops/sec
  Mean execution time: 363.79 ms
  Standard deviation: 135.01 ms
  Margin of error: ±18.46%
  Sample size: 18 runs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And for &lt;strong&gt;pooled:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Opening and closing raw connection x 348 ops/sec ±195.81% (87 runs sampled)
Opening and closing connection x 122,531 ops/sec ±0.85% (83 runs sampled)
INSERTING records in transaction x 8.81 ops/sec ±1.53% (45 runs sampled)
READING records x 27.28 ops/sec ±1.38% (66 runs sampled)

Benchmark: Opening and closing raw connection
  Operations per second: 347.82 ops/sec
  Mean execution time: 2.88 ms
  Standard deviation: 26.79 ms
  Margin of error: ±195.81%
  Sample size: 87 runs

Benchmark: Opening and closing connection
  Operations per second: 122531.07 ops/sec
  Mean execution time: 0.01 ms
  Standard deviation: 0.00 ms
  Margin of error: ±0.85%
  Sample size: 83 runs

Benchmark: INSERTING records in transaction
  Operations per second: 8.81 ops/sec
  Mean execution time: 113.46 ms
  Standard deviation: 5.92 ms
  Margin of error: ±1.53%
  Sample size: 45 runs

Benchmark: READING records
  Operations per second: 27.28 ops/sec
  Mean execution time: 36.66 ms
  Standard deviation: 2.10 ms
  Margin of error: ±1.38%
  Sample size: 66 runs&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;They’re clearly showing that Dumbo adds some overhead, but not too much. For the pooled setup, it works even better.&lt;/p&gt;
&lt;p&gt;By comparing the SQL query and reconnection results, it’s clear that the majority of the time is spent establishing a connection. &lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;See, I told you!&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;whats-next&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#whats-next&quot; aria-label=&quot;whats next permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;What’s next?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;If that was the conclusion for the tool that’s not intended to be serverless-friendly, we could bury the results in the new backlog issue. That could be a tradeoff if we had more important items. Still, I intend to make Emmett and Pongo serverless-friendly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next steps are:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Adding explicit connection session.&lt;/strong&gt; Thanks to that, the command handler could explicitly ask to reuse the same connection both for reads and appends. That should cut the execution time around 4 times for the non-pooled connection. Api will be similar to &lt;a href=&quot;https://www.mongodb.com/docs/manual/core/read-isolation-consistency-recency/#std-label-sessions&quot;&gt;Mongo sessions&lt;/a&gt;. Pongo already supports it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add upfront schema generation through CLI.&lt;/strong&gt; This should reduce the potential cold start and redundant call for schema creation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Adding Open Telemetry and/or extending benchmarks with &lt;a href=&quot;https://www.brendangregg.com/flamegraphs.html&quot;&gt;Flame Graphs&lt;/a&gt;.&lt;/strong&gt; This is critical not to blindly guess, but to have enough information about bottlenecks. Tracing and metrics will be essential. I already exposed benchmarks in the repository so anyone could run it. Giving more data is also essential for building trust in users.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add more benchmarks and load tests and expose the results openly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;I want to show you the open telemetry part. I already got feedback through the &lt;a href=&quot;https://forms.gle/qHekCiSxmAJnamh87&quot;&gt;form under the last article&lt;/a&gt; that this topic should interest you.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;tldr&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tldr&quot; aria-label=&quot;tldr permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;TLDR&lt;/strong&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Connection pools are improving performance by a lot.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;But you can’t assume that you’ll always be able to use it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;When you have performance issues, start by correcting obvious mistakes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then, understand the scenario, set up and write benchmarks to reproduce the issue (at least to some defined threshold).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use tools for that; don’t build them on your own.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Analyse the results and be critical of both them and your implementation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Check if they don’t have a huge deviation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adjust them or implement them if needed. Rinse repeat.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;There are lies, big lies, and benchmarks. We should be careful and critical.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The benchmarks I show you are really simple, but following this, you can run the initial investigations and the first round of improvements. Benchmarking is not easy; I’m also not an expert, but even not being an expert can give you good enough results if you tackle that systematically.&lt;/p&gt;
&lt;p&gt;To learn how to do it check a great intro from Daniel Marbach:&lt;/p&gt;
&lt;p&gt;&lt;div class=&quot;gatsby-resp-iframe-wrapper&quot; style=&quot;padding-bottom: 50%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em&quot; &gt; &lt;div class=&quot;embedVideo-container&quot;&gt; &lt;iframe title=&quot;Embedded video&quot; src=&quot;https://www.youtube-nocookie.com/embed/QI1vmZxINjc?rel=0&amp;amp;autoplay=0&amp;amp;showinfo=0&amp;amp;enablejsapi=0&quot; class=&quot;embedVideo-iframe&quot; style=&quot;border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; &quot; loading=&quot;lazy&quot; allowfullscreen=&quot;&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;&gt;&lt;/iframe&gt; &lt;/div&gt; &lt;/div&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And hey, dear reader, I’d like to learn where you’d like to go next. Your thoughts mean much to me, and I’d like to make it engaging for you! Please respond to this email, comment, or fill out this short survey: &lt;a href=&quot;https://forms.gle/1k6MS1XBSqRjQu1h8&quot;&gt;https://forms.gle/1k6MS1XBSqRjQu1h8&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Using event metadata in event-driven projections]]></title><description><![CDATA[Some time ago, I wrote about the dangers that come from the I’ll just add one more field” attitude. Have you heard about the Broken Window…]]></description><link>https://event-driven.io/en/projections_and_event_metadata/</link><guid isPermaLink="false">https://event-driven.io/en/projections_and_event_metadata/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d3b3f36478db62c37631cf815bf85de9/a331c/2024-08-25-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4dAAAuHQEHEELWAAADE0lEQVR42g2O7U8SAQDG73Ot5dp8qS0TESEEeQsOuOPgOO6Ol+PlDg4EOQIVOD1RUAHNVEAwAWvW0lqWW6318ilrtrW+1Fqu9aX3tfXfxPZ8+H15fs8D9GtGOVz992a8OI6N2iCrj0C8uNll16EmFaQjY7QvGab5hMaJWdQD31uxD9WxZwLxOI3uJRFAbjFG3eCvNteYD3tTY0qVjAi5lyoFA2HtWIwYJJaKhuVDBo/DZpT/20//3Y3/2GI/rodWIzAggcAgpv1SZSsZSmHSGfWKuUohlZ/U4xbEaWUzccRHghisw62IXvq7HftaDx2tBI9b2YO1FKB2IAmX/lMtOh/FQFhbKXFPX96igqTOCsI4zJeyNx4295/dpq8GQbXkpJV8VYnnStON25Xnrw8AKaiNuQwnrYnltF+ilhtUYoxCHX78CgYTPgdCYW6GZDi6swzphj/Xx4+3p7jSrLA2v7G1CFyQDU0G4D9709UZWqodsaN6zIs5AqQWMTg8qDtCeSOejmLUajJpJCfb3HGbpzOJTHk66DQCA7Khzu2fbW4xQQ6OXkY9KOFF3T67zW4w2U2QC4UJRG8FB5RSu0H2Z59/UUvJFENWszKR8AFynTJglr1dplm76szZU6QD3FhMMMbhz0dN1YhoUDYoGugTS/q7L3SPiM+XomiOgc71nNNjZjZGAaEw6TTKG3FbPc/OZgLbVf75g7W24Pv2plGepuemqFySLGY9C1NOBtey8Mh1nt65WW7vVR8cbgNFIawT9xRD0LrA5KIYH7Z1ks8wuSRVXYjuLI3dKMf3VmO7eb8bVuCKixMUmOfZbNyVu+oBDh/VRb1nU7hqgtAITk3BpWqmXY8a/EF16kkldTcf2inGm4K/PRcKkgaLSVHg2dZmbnM22OQpoEO4WbEisAvj+LUkWYtZymH4vuC+m2das4E7S5GtLFXk8J319FzCFaEgxm+bHCPSXjCGKoHatfRuPXcrH9xN2g4KgVqK7DTfrfrfb4bvFQJHhxv1Gf91IcTRNkgjGbzU29d9pqvrdE9vl6i/5z/QtQ3qjHYHUwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/d3b3f36478db62c37631cf815bf85de9/507e6/2024-08-25-cover.webp 200w,
/static/d3b3f36478db62c37631cf815bf85de9/28a80/2024-08-25-cover.webp 400w,
/static/d3b3f36478db62c37631cf815bf85de9/8d2ea/2024-08-25-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/d3b3f36478db62c37631cf815bf85de9/36ca5/2024-08-25-cover.png 200w,
/static/d3b3f36478db62c37631cf815bf85de9/a3397/2024-08-25-cover.png 400w,
/static/d3b3f36478db62c37631cf815bf85de9/a331c/2024-08-25-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/d3b3f36478db62c37631cf815bf85de9/a331c/2024-08-25-cover.png&quot;
            alt=&quot;2024 08 25 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some time ago, I wrote about the dangers that come from the &lt;a href=&quot;/en/i_will_just_add_one_more_field/&quot;&gt;I’ll just add one more field”&lt;/a&gt; attitude.&lt;/strong&gt; Have you heard about the &lt;a href=&quot;https://web.archive.org/web/20090418141450/http://www.theatlantic.com//doc//198203//broken-windows&quot;&gt;Broken Window Theory&lt;/a&gt;? Authors (James Q. Wilson and George L. Kelling) wrote:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Social psychologists and police officers tend to agree that if a window  in a building is broken and is left unrepaired, all the rest of the  windows will soon be broken. This is as true in nice neighborhoods as in rundown ones. Window-breaking does not necessarily occur on a large  scale because some areas are inhabited by determined window-breakers  whereas others are populated by window-lovers; rather, one un-repaired  broken window is a signal that no one cares, and so breaking more  windows costs nothing. (It has always been fun.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Adam Tornhill tells us to treat our &lt;a href=&quot;https://pragprog.com/titles/atcrime2/your-code-as-a-crime-scene-second-edition/&quot;&gt;code as a crime scene&lt;/a&gt;, and to some degree, we should. In event modelling, adding new property can be such a broken window if we’re doing it without considering alternatives and tradeoffs. Yet, sometimes, it can be a decent option. When? Let’s discuss it; that’s what we’re here for!&lt;/p&gt;
&lt;p&gt;Let’s use this blog’s favourite example: the shopping cart. Let’s say we defined the following events (in TypeScript, but treat it just as syntax):&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItemAddedToShoppingCart&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAddedToShoppingCart&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    addedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItemRemovedFromShoppingCart&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemRemovedFromShoppingCart&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    removedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartConfirmed&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    confirmedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartCancelled&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartCancelled&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    cancelledAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartEvent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ProductItemAddedToShoppingCart
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ProductItemRemovedFromShoppingCart
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ShoppingCartConfirmed
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ShoppingCartCancelled&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItem&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PricedProductItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ProductItem &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  unitPrice&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;They show what can happen in our shopping cart’s lifetime.&lt;/strong&gt; They’re &lt;a href=&quot;/en/events_should_be_as_small_as_possible/&quot;&gt;as small as possible, but not smaller&lt;/a&gt;. They’re granular and focused on business but have some potential redundancy by all having shopping cart id. I want to keep events expressive and telling what has happened. I wrote in another article on &lt;a href=&quot;/en/on_putting_stream_id_in_event_data/&quot;&gt;the danger of slicing too much from events&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Of course, this is the grey area and a careful act of balancing where to put where.&lt;/strong&gt; For instance, I put the client ID only in product items added to the shopping cart. I expect it to be always the first event in the stream, and I don’t want to repeat it. Why? Because the stream represents a shopping cart, keeping the shopping cart id can be seen as explicit information about the context; the client id is an additional reference to another stream. That’s why I’m not repeating the client id in other events. But maybe I should?&lt;/p&gt;
&lt;p&gt;What if we’d like to have the read model that aggregates the general summary of client’s pending, confirmed and cancelled shopping carts? It could be defined as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ClientShoppingSummary&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  pending&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PendingSummary &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  confirmed&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConfirmedSummary&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  cancelled&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; CancelledSummary&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingSummary&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PendingSummary&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ShoppingSummary &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  cartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ConfirmedSummary&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ShoppingSummary &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  cartsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;CancelledSummary&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ShoppingSummary &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  cartsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It contains the pending shopping cart information (if there’s such a thing) plus the total number of confirmed and cancelled shopping carts, their total amounts, and total product item counts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The id of our read model is equal to the client id. Every client will have a single summary.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To build this read model, we need to correlate events with respective records. We’ll be applying events sequentially. We need to know which record they need to update. If our read model id is equal to the client id, then best if we have the client id in events. But besides the Product Item Added event, we don’t.&lt;/p&gt;
&lt;p&gt;Of course, we could reconsider adding it to all the events, but we already discussed that we would not necessarily like to. So what should we do?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We could query some other read model (e.g. shopping cart details) and load the client id, but then we’d have an even worse problem.&lt;/strong&gt; Tying those two models together and decreasing scaling and isolation. That’s an important aspect, &lt;a href=&quot;https://event-driven.io/en/projections_and_read_models_in_event_driven_architecture/#scaling-and-data-isolation&quot;&gt;I wrote about it here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Still, if we think that business-wise, data should always be there, then we could use event metadata.&lt;/strong&gt; Besides the regular data, events can also have metadata, so common information that is typically used for some generic processing could also (if needed) be used for business processing. That’s always a steep hill, and you better be careful not to make metadata a “bag for random data.” This definitely can be a hidden trap. There are no hard rules here, but some good heuristics.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We can look at our endpoints and commands that initiate business logic, resulting in events.&lt;/strong&gt; For instance, if we look at:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;POST /clients/:clientId/shopping-carts/current/product-items

DELETE /clients/:clientId/shopping-carts/current/product-items

POST /clients/:clientId/shopping-carts/current/confirm

DELETE /clients/:clientId/shopping-carts/current

GET /clients/:clientId/shopping-carts/current&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then, we see that all of them are in the current shopping cart context and the specific client. That can lead to the conclusion that we already have this client context in our requests. Maybe it’s used for authorisation, maybe for tenancy reasons.&lt;/p&gt;
&lt;p&gt;If that’s not visible in endpoints, we can check on our authorisation rules and middleware. They typically need some data based on the currently authenticated user.&lt;/p&gt;
&lt;p&gt;Having that, we could consider making the client id a part of the shopping cart event metadata. This could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartEventMetadata&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItemAddedToShoppingCart&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAddedToShoppingCart&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    addedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ShoppingCartEventMetadata
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItemRemovedFromShoppingCart&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemRemovedFromShoppingCart&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    removedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ShoppingCartEventMetadata
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartConfirmed&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    confirmedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ShoppingCartEventMetadata
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartCancelled&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartCancelled&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    cancelledAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  ShoppingCartEventMetadata
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we &lt;a href=&quot;https://github.com/event-driven-io/emmett/blob/49d2b1fb1444f0fb129c327a27c06b7ec7c94ff2/samples/webApi/expressjs-with-postgresql/src/shoppingCarts/businessLogic.ts#L25&quot;&gt;define also such metadata for our commands&lt;/a&gt;, we can pass it as that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt; &lt;span class=&quot;token comment&quot;&gt;// Confirm Shopping Cart&lt;/span&gt;
router&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;/clients/:clientId/shopping-carts/current/confirm&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; clientId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;assertNotEmptyString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCartId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getShoppingCartId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;assertNotEmptyString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConfirmShoppingCart &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ConfirmShoppingCart&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; shoppingCartId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;// 👇 See what we did here&lt;/span&gt;
      metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; clientId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; now&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getCurrentTime&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eventStore&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;confirm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;command&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; state&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;NoContent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Business logic&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; confirm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  command&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ConfirmShoppingCart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  state&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCart&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartConfirmed &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Opened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping Cart is not opened&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; totalQuantityOfAllProductItems &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItems&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;totalQuantityOfAllProductItems &lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IllegalStateError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping Cart is empty&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; shoppingCartId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    metadata&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; command&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      confirmedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; metadata&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;now &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// 👇 See what we did here&lt;/span&gt;
    metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; metadata&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;  
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I’m using &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html#webapi-definition&quot;&gt;TypeScript, Express.js and Emmett&lt;/a&gt;, but I’m sure that you can translate that to your favourite language and tech stack.&lt;/p&gt;
&lt;p&gt;As we now have the client ID in metadata, we could use it for the event to read model correlation. For instance, using &lt;a href=&quot;/en/emmett_projections_testing/&quot;&gt;Emmett+Pongo projections&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; clientShoppingSummaryCollectionName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ClientShoppingSummary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; clientShoppingSummaryProjection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoMultiStreamProjection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  collectionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; clientShoppingSummaryCollectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// 👇 See what we did here&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;getDocumentId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  canHandle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAddedToShoppingCart&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemRemovedFromShoppingCart&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartCancelled&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re saying that to find the document ID for each shopping cart event, you can use &lt;em&gt;event.metadata.clientId&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Then, the projection definition can look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; evolve &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ClientShoppingSummary &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; metadata &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartEvent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ClientShoppingSummary &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; summary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ClientShoppingSummary &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    clientId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; metadata&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;clientId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    pending&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    confirmed&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; initialSummary&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    cancelled&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; initialSummary&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAddedToShoppingCart&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;summary&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        pending&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          cartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            summary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pending&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemRemovedFromShoppingCart&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;summary&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        pending&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          cartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            summary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pending&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;removing&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;summary&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        pending&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        confirmed&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          cartsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;confirmed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cartsCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            summary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;confirmed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pending&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartCancelled&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;summary&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        pending&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        cancelled&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          cartsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;confirmed&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cartsCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            summary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;confirmed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pending&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; summary&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; initialSummary &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  cartsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; withAdjustedTotals &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  summary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingSummary &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ShoppingSummary&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;removing&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; summary&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; by &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; totalAmount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;totalAmount&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;with
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;with&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalAmount
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;with&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unitPrice &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;with&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; productItemsCount &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&apos;productItemsCount&apos;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;with
      &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;with&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItemsCount
      &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;with&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; plusOrMinus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; by &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;totalAmount &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; totalAmount &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; plusOrMinus&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token operator&quot;&gt;?.&lt;/span&gt;productItemsCount &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productItemsCount &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; plusOrMinus&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Is it the best option? It depends on the use case. I’d always treat it as a tradeoff and evaluate if there are other options (e.g. &lt;a href=&quot;/en/event_transformations_and_loosely_coupling/&quot;&gt;in-flight event transformations&lt;/a&gt;). Still, I hope this article gives you enough context for the tradeoff analysis and making educated decisions in your design.&lt;/p&gt;
&lt;p&gt;Read also more in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/i_will_just_add_one_more_field/&quot;&gt;Anti-patterns in event modelling - I’ll just add one more field&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/on_putting_stream_id_in_event_data/&quot;&gt;Stream ids, event types prefixes and other event data you might not want to slice off&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/events_should_be_as_small_as_possible/&quot;&gt;Events should be as small as possible, right?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/projections_and_read_models_in_event_driven_architecture/&quot;&gt;Event transformations, a tool to keep our processes loosely coupled&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/how_to_create_projections_of_events_for_nested_object_structures/&quot;&gt;How to create projections of events for nested object structures?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/event_transformations_and_loosely_coupling&quot;&gt;Guide to Projections and Read Models in Event-Driven Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Is It Production Ready?]]></title><description><![CDATA[Welcome to the new week! Why is connection pooling important? How can queuing, backpressure, and single-writer patterns help? If you don’t…]]></description><link>https://event-driven.io/en/architecture-weekly-191-is-it-production/</link><guid isPermaLink="false">https://event-driven.io/en/architecture-weekly-191-is-it-production/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why is connection pooling important? How can queuing, backpressure, and single-writer patterns help?&lt;/strong&gt; If you don’t know the answer, then go go go, and check the previous two editions (&lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;/en/architecture-weekly-190-queuing-backpressure/&quot;&gt;there&lt;/a&gt;)!&lt;/p&gt;
&lt;p&gt;Jokes aside, we did a learning by doing. We’ve built a simple connection pool, added queuing with backpressure, and implemented patterns like single-writer. All of that is to show, on a smaller scale, how those patterns can be extrapolated to broader usage.&lt;/p&gt;
&lt;p&gt;We wrote it in TypeScript, and the code looks solid; it sounds like we could deploy it to production or maybe pack it and release it as an open-source library, right?&lt;/p&gt;
&lt;p&gt;Hold your horses! Before we hit deploy, there’s a usual question we need to ask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is this ready for production?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If we don’t ask ourselves, we’ll either ask soon or learn the answer the hard way.&lt;/p&gt;
&lt;p&gt;This is the usual border between the proof of concept and reality. Even if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;something conceptually makes sense,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we talked through it with our colleagues,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we tested it on a smaller scale, we should validate it before applying it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How to do it? We’ll discuss that today.&lt;/p&gt;
&lt;h2 id=&quot;what-does-production-readiness-even-mean&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-does-production-readiness-even-mean&quot; aria-label=&quot;what does production readiness even mean permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;What does production-readiness even mean?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Does this mean it’s feature-complete, has no bugs, or doesn’t have security leaks? What about working as expected? Expected from who? Users, customers, operations?&lt;/p&gt;
&lt;p&gt;What’s your definition?&lt;/p&gt;
&lt;p&gt;Google invented the whole concept of &lt;a href=&quot;https://sre.google/sre-book/evolving-sre-engagement-model/&quot;&gt;the Production Readiness Review&lt;/a&gt;. The name is so posh that it clearly says it’s hard to define what production means in general.&lt;/p&gt;
&lt;p&gt;We cannot define a simple checklist that fits all cases, but we can decide if our software matches our contextual criteria.&lt;/p&gt;
&lt;p&gt;In our case, the code was production-ready as it matched the criteria of being an illustration for a discussion about the architecture concept. Still, I tested it today, and it didn’t even compile (sloppy me!). And that’s not the end of the list.&lt;/p&gt;
&lt;p&gt;Let’s think about verifying if our connection pool and queue are production-ready in a more common sense.&lt;/p&gt;
&lt;h2 id=&quot;1-unit-testing-so-how-to-verify-that&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#1-unit-testing-so-how-to-verify-that&quot; aria-label=&quot;1 unit testing so how to verify that permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;1. Unit Testing, so how to verify that?&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Testing is more than just verifying that the code works - it’s about ensuring it holds up under all specified conditions.&lt;/strong&gt; Some say that we should always write the tests first. Test means specification and verification are here if the code matches it. We didn’t do that, as the code was more an illustration of the idea, but if we wanted to make it work, we’d need to run it finally. Of course, we could write an application using our connection pool, run it and manually test it. Yet, that wouldn’t be repeatable and would be a good measure. We could easily skip some edge cases. We need a more systematic approach.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We could start with unit testing.&lt;/strong&gt; What a unit means is an endless discussion. For the sake of this article, let’s assume that unit tests are where we validate the smallest possible components of our system in isolation (or actually their behaviour). We need to ensure that each function does what it’s supposed to, without surprises.&lt;/p&gt;
&lt;p&gt;In our case, this could mean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;QueueBroker Initialization&lt;/strong&gt;: The &lt;code class=&quot;language-text&quot;&gt;QueueBroker&lt;/code&gt; is the heart of our queuing system. When it starts, it should initialise with the correct options, have an empty queue, and be ready to accept tasks immediately. If it fails here, nothing else will work correctly. We’ll test to confirm this foundational behavior.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Task Enqueuing&lt;/strong&gt;: Tasks need to be added to the queue correctly. The system should respect the &lt;code class=&quot;language-text&quot;&gt;maxQueueSize&lt;/code&gt; limit—if we try to enqueue beyond that, it should reject the tasks cleanly. It is crucial to ensure that the system won’t overwhelmed under high load, potentially leading to a crash.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Task Processing&lt;/strong&gt;: The &lt;code class=&quot;language-text&quot;&gt;processQueue&lt;/code&gt; method is responsible for managing active tasks. It must handle tasks without exceeding the &lt;code class=&quot;language-text&quot;&gt;maxActiveTasks&lt;/code&gt; limit. This prevents resource exhaustion and ensures that the system can maintain performance under load. We’ll test how it deals with successful and failed tasks to ensure resilience.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Graceful Shutdown&lt;/strong&gt;: When the system needs to shut down, the &lt;code class=&quot;language-text&quot;&gt;end&lt;/code&gt; method should stop accepting new tasks while allowing current tasks to finish. This is critical to avoid leaving work in an inconsistent state, which could lead to data corruption. We’ll ensure the shutdown process is smooth, even when &lt;code class=&quot;language-text&quot;&gt;end&lt;/code&gt; is called multiple times.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Connection Management&lt;/strong&gt;: We’ll test how the connection pool handles opening and releasing connections. It must respect the &lt;code class=&quot;language-text&quot;&gt;maxConnections&lt;/code&gt; limit and efficiently trigger the next task when a connection is released. It ensures that the system uses resources efficiently without bottlenecks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-integration-testing-ensuring-components-play-nicely-together&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#2-integration-testing-ensuring-components-play-nicely-together&quot; aria-label=&quot;2 integration testing ensuring components play nicely together permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;2. Integration Testing: Ensuring Components Play Nicely Together&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Unit tests are great for individual components, but we must see how everything works together. Integration tests simulate real-world scenarios to ensure the entire system functions as expected.&lt;/p&gt;
&lt;p&gt;If I were to start testing, I wouldn’t probably start with unit tests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In our case, I’d start with end-to-end task execution.&lt;/strong&gt; I’d write tests simulating operations like opening and releasing database connections to ensure tasks are processed in the correct order and within the system’s configured limits. Then, depending on the outcome of that execution, I’d cover failing scenarios with unit tests, gradually building trust in the implementation. Of course, we’d like to cover everything in the end, but our implementation currently doesn’t do any IO operations. So, testing with integration tests should be fast enough. I’d also keep the focus on the business scenarios, allowing us to refactor the internals more easily.&lt;/p&gt;
&lt;p&gt;What else could we check by integration tests?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Failure Handling&lt;/strong&gt;: Real systems encounter failures. If a task fails, the system should continue processing other tasks without issues. We could test whether a single failure doesn’t bring down the entire system and whether error handling works as expected.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Edge Case Handling&lt;/strong&gt;: Every system has edge cases, which are unexpected scenarios that can cause problems if mishandled. We could test situations like restarting the pool or enqueuing tasks after the pool has ended. The system needs to behave predictably even in these less common situations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backpressure Simulation&lt;/strong&gt;: Queuing and backpressure are about managing overload. We need to simulate scenarios where the queue fills rapidly to see if the system rejects new tasks correctly and recovers when the load decreases. This is critical for ensuring that the system remains responsive under stress.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Single Writer Guarantees&lt;/strong&gt;: We could try to access the connection pool concurrently, asynchronously, multiple times to ensure that it doesn’t misbehave, so, e.g. that it keeps the limits of max connections and parallel processing and doesn’t become stuck in the middle when all connections are closed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As we’re coding in Node.js and TypeScript, I’d use &lt;a href=&quot;https://nodejs.org/api/test.html&quot;&gt;Native Node.js Test Runner&lt;/a&gt;. I could also use some other popular solutions like &lt;a href=&quot;https://jestjs.io/&quot;&gt;Jest&lt;/a&gt;, &lt;a href=&quot;https://vitest.dev/&quot;&gt;Vitest&lt;/a&gt;, etc.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The decision would be related to the team’s and organisation’s experience.&lt;/strong&gt; I’d try to balance the learning time and avoid using obsolete or unmaintained tooling (even if the team knows it).&lt;/p&gt;
&lt;p&gt;Yet, as the Node.js native runner is built-in, delivered by the core team, and it’s fast, I’d use it to reduce the number of dependencies and moving pieces.&lt;/p&gt;
&lt;h2 id=&quot;3-stress-testing-pushing-the-system-to-its-limits&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#3-stress-testing-pushing-the-system-to-its-limits&quot; aria-label=&quot;3 stress testing pushing the system to its limits permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;3. Stress Testing: Pushing the System to Its Limits&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;It’s not enough to know that the system works under normal conditions - we need to see how it performs when it’s really under pressure. Stress testing pushes the system to its limits to identify potential failure points and understand its behaviour in extreme scenarios.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;High Concurrency Load&lt;/strong&gt;: We could simulate a high number of concurrent tasks to see how the connection pool handles the load. Does it keep up, or do we start seeing delays and failures? It helps us identify bottlenecks in task processing and resource management.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Queue Size Limits&lt;/strong&gt;: We need to understand what happens when the queue is full (or almost full). Does it manage resources efficiently, or does it start to fail? Testing this helps ensure stability as it approaches its limits.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resource Utilisation&lt;/strong&gt;: Under stress, we could monitor CPU and memory usage to spot any bottlenecks or inefficiencies. We should understand how our solution scales and where improvements might be needed to handle higher loads. We should always do that knowing the usage characteristics and consider deployment limits and costs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Task Execution Timeouts&lt;/strong&gt;: By introducing delays, we can simulate long-running tasks to see if the system can handle these without causing delays for other tasks. This helps us ensure the system remains responsive, even when some tasks take longer than expected. We can also detect if that won’t cause cascading failures. For instance, having the maximum number of tasks, most of them long-running, won’t harm our load. This part can also be used to fine-tune the default settings recommendations (like queue size, maximum connection limit, etc.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Graceful Shutdown:&lt;/strong&gt; One of the most critical aspects of a robust system is how it shuts down. If the system can’t stop cleanly, it risks leaving tasks incomplete or data inconsistent. We could simulate scenarios where the pool is instructed to shut down. Then we’d need to ensure that no new tasks are enqueued, all pending tasks are completed, and the system doesn’t leave any connections open or tasks unprocessed. Such verification is essential for maintaining data integrity and avoiding resource leaks, which could affect future system performance or reliability.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-load-testing-ensuring-stability-under-realistic-conditions&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#4-load-testing-ensuring-stability-under-realistic-conditions&quot; aria-label=&quot;4 load testing ensuring stability under realistic conditions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;4. Load Testing: Ensuring Stability Under Realistic Conditions&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Stress testing is about handling extreme conditions—like sudden spikes in traffic or resource exhaustion. It might not reveal issues that only appear during prolonged, sustained usage. That’s where load tests are must-haves; they ensure that the system performs reliably under expected, everyday conditions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simulating Typical User Load:&lt;/strong&gt; We can simulate the number of concurrent users accessing the system, ensuring that the connection pool and queue manage tasks efficiently under normal operating conditions. This helps us validate that response times remain consistent and that no unexpected delays or bottlenecks occur during regular use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Memory Management Over Time:&lt;/strong&gt; Load tests allow us to monitor memory usage over extended periods. This is essential for detecting memory leaks, where memory consumption gradually increases without release, potentially leading to crashes or slowdowns after prolonged operation. It ensures that our connection pool and queue do not introduce memory inefficiencies that could degrade system performance over time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resource Consumption Stability:&lt;/strong&gt; we can track the system’s CPU, memory, and other resource usage under sustained load by running load tests. This helps us ensure that resource consumption remains steady and predictable, without unexpected spikes or gradual increases that could indicate inefficiencies or potential points of failure in the connection management process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Latency and Throughput Consistency:&lt;/strong&gt; Load testing lets us measure whether the system maintains consistent response times and task processing rates as it handles typical user activity. Suppose we observe increasing latencies or reduced throughput under load. In that case, it may indicate bottlenecks in the queue management or connection pooling logic that must be addressed to ensure a smooth user experience.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Connection Pool Utilisation:&lt;/strong&gt; we monitor how effectively the connection pool manages available connections during load testing. We need to ensure that connections are reused efficiently and that the pool scales to handle varying levels of demand without unnecessary delays in connection handling. This helps us confirm that the system is optimally configured for real-world use.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Real-World Scenario Testing:&lt;/strong&gt; Load testing allows us to simulate more complex scenarios that might occur in production, such as a gradual increase in user activity throughout the day or handling intermittent traffic spikes. This helps us ensure the system remains stable and responsive as usage patterns fluctuate, providing insights into how well it adapts to varying workloads.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For our scenario, I’d use &lt;a href=&quot;https://benchmarkjs.com/&quot;&gt;Benchmark.js&lt;/a&gt; for microbenchmarks and &lt;a href=&quot;https://k6.io/&quot;&gt;k6&lt;/a&gt; for load and stress testing. What is the difference? Think of microbenchmarks as unit tests. They can benchmark precisely small components, but they won’t show the whole picture. We need both. In the same way, we could start with load and stress testing, micro-benchmarking precisely the bottlenecks.&lt;/p&gt;
&lt;p&gt;Both Benchmark.js and k6 allow to code tests in JS, which matches our needs!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2599a9b9eeadb255cde8f432e766bff9/44e52/2024-08-19-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAACmUlEQVR42qWSW0sbQRSAd3f2no3ZW5PNNvUSYgsaL8VG06Im1iRFhErxUV9asPQfCNI/ID6W+FDoQ7H9GaUPfS1YrYnZ2dk1VqtYMT+gD6VnNfECQoUO+zFnZ5iPc84MRcHY+/I5+63y6cUW3p7zPG++RQ34uo3nKpXN+U1gfX19/vJ+kzlCyPONjY0s1Rozhcfvns48+fNsdvZkenq6USiWGqVSqTEJ8+jEZCOXyzVy+XwjDxSLxSsUCoXjqamp36urq2vUxaDfAD8omt6iWa5KMWibYpgqbFRuwHeWZXdWVlben9t4USyL4fABFwrVAMwpistHIi6vqiSIAxiGcRiaPiOIm9A0XeV5fh+EaxdCni+nUqkj1Yo7lCxjRpIwkmUXKWEXhRSXPZsxkmSMRAmzHOfAuRZVyHB/eXn5QkjTqGxZ1qEWiznRRIIoquZC6ZgWBExzAmYEETMgCmIUxCz7DyFCZdM0DzXIULNtIhumy4dCLgslsSzCLELOKU0RQggDDpy8Xghf2TDMQ900azL0KxKLkbCuuyEoPQQtuAyUU5MkyREEIZDVrhVy0MN0Ov3r/uAg0SJhEFok3Jn0FE13JdMkkWiUSIbhyrrhGlacqBbsm6ZrxG1CSVINst+7Igwupaen92gin/cG+vr82/E4ST4c3b07kq23D2frPaPj9fbMSD0J9D4a202P53b7xsbrdx5k6qqdwHxb5OfVDDmu3N8/cJzJDBPTMIgG5d5KdftmR6end3T60a6kryY6vHgww1pX9z3fSCZ9BbJXozHMa/rB66WlD+fPWm1re2vb9okoigTe1g7i+R1Y9m+ICz08Wlxc/NjyMYqi5ED0CliA/5cBEN+UheDs0NBQPnA1b/kU9j8JZPRfk+EbtW3sHB4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/2599a9b9eeadb255cde8f432e766bff9/507e6/2024-08-19-cover.webp 200w,
/static/2599a9b9eeadb255cde8f432e766bff9/28a80/2024-08-19-cover.webp 400w,
/static/2599a9b9eeadb255cde8f432e766bff9/8d2ea/2024-08-19-cover.webp 800w,
/static/2599a9b9eeadb255cde8f432e766bff9/68fc1/2024-08-19-cover.webp 1200w,
/static/2599a9b9eeadb255cde8f432e766bff9/43d96/2024-08-19-cover.webp 1600w,
/static/2599a9b9eeadb255cde8f432e766bff9/eb2fa/2024-08-19-cover.webp 1618w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/2599a9b9eeadb255cde8f432e766bff9/36ca5/2024-08-19-cover.png 200w,
/static/2599a9b9eeadb255cde8f432e766bff9/a3397/2024-08-19-cover.png 400w,
/static/2599a9b9eeadb255cde8f432e766bff9/a331c/2024-08-19-cover.png 800w,
/static/2599a9b9eeadb255cde8f432e766bff9/8537d/2024-08-19-cover.png 1200w,
/static/2599a9b9eeadb255cde8f432e766bff9/1a152/2024-08-19-cover.png 1600w,
/static/2599a9b9eeadb255cde8f432e766bff9/44e52/2024-08-19-cover.png 1618w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/2599a9b9eeadb255cde8f432e766bff9/a331c/2024-08-19-cover.png&quot;
            alt=&quot;The state of load testing&quot;
            title=&quot;The state of load testing&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://k6.io/blog/the-state-of-load-testing/&quot;&gt;https://k6.io/blog/the-state-of-load-testing/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;5-observability-gaining-full-visibility-into-the-system&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#5-observability-gaining-full-visibility-into-the-system&quot; aria-label=&quot;5 observability gaining full visibility into the system permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;5. Observability: Gaining Full Visibility into the System&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Last but not least, to track precisely what’s happening in our system, we need to make our connection pool and queue observable. This involves publishing metrics such as the current number of open connections, queue depth, error rates, and any other data that helps us understand the system’s behaviour, especially when things go wild.&lt;/p&gt;
&lt;p&gt;Incorporating observability isn’t just about plugging in a few tools - it’s about making informed decisions on monitoring, tracing, and logging what’s happening under the hood.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/97b7f0ce82f3e3b184e9304f5fee9405/image-2.svg&quot; alt=&quot;OpenTelemetry Reference Architecture&quot; title=&quot;OpenTelemetry Reference Architecture&quot;&gt;&lt;/p&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://opentelemetry.io/docs/&quot;&gt;https://opentelemetry.io/docs/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-opentelemetry&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-opentelemetry&quot; aria-label=&quot;why opentelemetry permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Why OpenTelemetry?&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;OpenTelemetry provides a comprehensive framework for collecting distributed traces, metrics, and logs from various parts of our system.&lt;/strong&gt; It’s an open-source, vendor-neutral standard that allows you to instrument your code without being locked into a specific monitoring solution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The trade-offs? OpenTelemetry’s flexibility is both its strength and a potential weakness.&lt;/strong&gt; While it allows you to collect and export data to different backends (like Prometheus, Jaeger, or Zipkin), the initial setup can be complex, especially in a large system with many moving parts. You’ll need to invest time in configuring it correctly and integrating it across your application stack.&lt;/p&gt;
&lt;p&gt;However, the benefits outweigh the setup effort, especially in a production environment where understanding the flow of data and pinpointing performance bottlenecks is crucial. OpenTelemetry lets us trace how tasks move through the queue, measure the time spent in various states, and capture any anomalies. This detailed tracing is vital for correlating performance issues with specific events or code paths, making diagnosing and resolving problems quickly easier.&lt;/p&gt;
&lt;p&gt;What tooling would I use for our Connection Pool and Queue observability?&lt;/p&gt;
&lt;h3 id=&quot;open-telemetry-tooling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#open-telemetry-tooling&quot; aria-label=&quot;open telemetry tooling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Open Telemetry tooling&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus&lt;/a&gt; and &lt;a href=&quot;https://grafana.com/&quot;&gt;Grafana&lt;/a&gt; are powerful for monitoring and visualising system metrics.&lt;/strong&gt; Prometheus excels at scraping and storing time-series data, such as the number of active connections, task processing times, and queue depth. Grafana, in turn, offers robust visualisation capabilities, allowing us to create dashboards that provide real-time insights into system performance.&lt;/p&gt;
&lt;p&gt;While powerful, Prometheus isn’t as straightforward when dealing with high-cardinality data (data with many unique labels). If your system generates many unique metrics (for example, tracking individual tasks in a highly dynamic environment), Prometheus can struggle with memory consumption and performance. Additionally, setting up Prometheus with Grafana involves more configuration than simpler, all-in-one solutions. The alternative is using vendor-managed solutions like Honeycomb, Datadog, etc.&lt;/p&gt;
&lt;p&gt;Still, Prometheus and Grafana are widely adopted, meaning extensive community support and a wealth of plugins and integrations are available.&lt;/p&gt;
&lt;h3 id=&quot;structured-logging&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#structured-logging&quot; aria-label=&quot;structured logging permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Structured Logging&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/pinojs/pino&quot;&gt;Pino&lt;/a&gt; is a high-performance logging library for Node.js that outputs logs in a structured JSON format.&lt;/strong&gt; This structured logging approach is essential because it makes it easier to search, analyse, and correlate logs with the metrics and traces collected by OpenTelemetry and Prometheus.&lt;/p&gt;
&lt;p&gt;Of course, nothing is perfect. Pino is designed for performance, so it might not offer some of the more advanced features available in other logging libraries (like Winston). For instance, while Pino is great for fast logging with minimal overhead, it might require additional setup to integrate with log management systems like Elasticsearch or &lt;a href=&quot;https://grafana.com/oss/loki/&quot;&gt;Loki&lt;/a&gt; for complex querying and alerting.&lt;/p&gt;
&lt;p&gt;Despite these trade-offs, Pino’s speed and efficiency make it an ideal choice for applications where performance is critical and logging overhead needs to be minimised. Moreover, its JSON output is perfectly suited for integration with other observability tools, making correlating log events with traces and metrics easier.&lt;/p&gt;
&lt;h3 id=&quot;the-importance-of-correlation&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-importance-of-correlation&quot; aria-label=&quot;the importance of correlation permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Importance of Correlation&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The real power of these tools lies in their ability to work together.&lt;/strong&gt; You gain a holistic view of your system by combining OpenTelemetry’s traces, Prometheus’s metrics, and Pino’s structured logs. This means when an issue arises, you’re not just seeing that something went wrong—you’re seeing why it went wrong, how it propagated through your system, and where to fix it.&lt;/p&gt;
&lt;p&gt;For example, if your load tests reveal that tasks are taking longer to process under certain conditions, OpenTelemetry traces can show you where the delays occur in your queue. Prometheus metrics can then indicate whether the problem is related to resource saturation (e.g., CPU or memory constraints). Pino logs can provide the exact error messages or unexpected behaviours that triggered the slowdown.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5566506f07444d11a255d8535681e778/a331c/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB/0lEQVR42h2SaWvbQBCG9f9/RCmYlJqWtAk1JYEE9ySpG7uOG8uyrGPvS3tIK9lpP3VUWC0Lmpn3mXcmce3RhkGa1rgolP25etxmRZoV6036/f5hX+CHX78Xy8en3eFQ0V2OlAkhHl0YID6xvldNJ0yrXb9cpy9eTiZnU7inb95NXk3PL2bnFx/uFqv3l7PL2dXZ67cF4sb3jYvG9QnXQdnY+AGSSywwM5hpxDQEFTVL9xWo7fJ6X5AS8UPNqHTSdgrEbJdAGcgnomHKNWGAI0yg0lLREGGJ9FQ5LBzmFooq24GMCb1uWngnXHkqLDQgmxZKED7mQL52g3Wt4VWVbVC+sUZw3WJuRNNJGwFzxAZB2z3b7uS6ZzAPlH0buziErjeydgqRcsuqrXd6DPMtowgOQbU0IdnmeF9yuHcFK6hF3FqaivJRlmtrAOuk/VGHk3S9ckdvKMkWxdMPg3fQRXKoeUV1SXRNDeIjNiYEEyyVFoJxUiglJMNaEMURpxW4C5qUGyZdImFO2sv/BgAzwPv4R/uBCCfGX8YpXKarKl1aBS0HMIhJK7Uf5wxfTXVeEtgHoMgKklcE9gGoCDeIGSocjINJjwEKDNMeZEa3mzbJuJt/vf94dXN9M1+sNvMvd9e3n28/fSsxb4e/sHxgFHCqJhgffTz5CO4eQVLb+A8S+krZkHXs+QAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5566506f07444d11a255d8535681e778/507e6/image-3.webp 200w,
/static/5566506f07444d11a255d8535681e778/28a80/image-3.webp 400w,
/static/5566506f07444d11a255d8535681e778/8d2ea/image-3.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5566506f07444d11a255d8535681e778/36ca5/image-3.png 200w,
/static/5566506f07444d11a255d8535681e778/a3397/image-3.png 400w,
/static/5566506f07444d11a255d8535681e778/a331c/image-3.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5566506f07444d11a255d8535681e778/a331c/image-3.png&quot;
            alt=&quot;cover&quot;
            title=&quot;cover&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-this-setup-is-necessary&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#why-this-setup-is-necessary&quot; aria-label=&quot;why this setup is necessary permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Why This Setup is Necessary&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Without observability, you’re essentially flying blind. You might know something is wrong when your application slows down or crashes, but you won’t have the necessary insights to diagnose and fix the issue efficiently. This is particularly critical in production environments, where downtime can have significant business impacts. The chosen tools aren’t just about collecting data—they’re about making that data actionable. With proper observability, you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Proactively identify and fix issues before they impact users.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Understand the root causes of performance bottlenecks and failures.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optimise your system using real-world usage patterns, not just theoretical assumptions.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In essence, while there are always trade-offs with tool selection—whether it’s complexity, performance overhead, or integration challenges—the combination of OpenTelemetry, Prometheus, Grafana, and Pino offers a balanced approach that provides deep insights, flexibility, and robust performance monitoring. These tools collectively ensure that your connection pool and queuing system aren’t just production-ready and equipped to handle the unexpected with grace.&lt;/p&gt;
&lt;h2 id=&quot;6automated-testing-with-cicd&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#6automated-testing-with-cicd&quot; aria-label=&quot;6automated testing with cicd permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;6. Automated Testing with CI/CD&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Of course, we need to integrate, automate and run it continuously. It’s not just about running tests - it’s about catching issues early and making sure everything in your codebase is continuously validated. This reduces the risk of introducing errors into production and keeps your code in a deployable state. I’d use GitHub Actions for that, as it’d be integrated with repository, making it easy to set up workflows for automating tests, deployments, and other processes.&lt;/p&gt;
&lt;p&gt;GitHub Actions can run easily both unit tests, and integration tests. You can use it to spin up Docker containers or test environments using &lt;a href=&quot;https://testcontainers.com/&quot;&gt;Testcontainers&lt;/a&gt;, which is especially useful for integration tests that require databases or other services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;While GitHub Actions is great for the basics, stress and load testing require more specialized tools and environments.&lt;/strong&gt; These tests simulate real-world usage and extreme conditions, which can be difficult to replicate within the limited environment of GitHub Actions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Environment Limitations:&lt;/strong&gt; Stress and load tests typically require a more controlled and scalable environment, often with the ability to simulate hundreds or thousands of concurrent users. GitHub Actions runners are not ideal for this, as they are designed for short-lived, smaller-scale operations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resource Intensive:&lt;/strong&gt; Stress and load tests can consume significant resources, including CPU, memory, and network bandwidth. Running these tests on GitHub Actions could be costly and inefficient, as the environments are not optimized for long-running, resource-heavy tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For that we’d either need some cloud solutions (&lt;a href=&quot;https://k6.io/docs/cloud/&quot;&gt;k6 provides such&lt;/a&gt;), or (God help!) Kubernetes deployment.&lt;/p&gt;
&lt;h2 id=&quot;7-overwhelmed-lets-keep-it-practical&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#7-overwhelmed-lets-keep-it-practical&quot; aria-label=&quot;7 overwhelmed lets keep it practical permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;7. Overwhelmed? Let’s Keep It Practical&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;If you’re starting to feel like there’s a lot to handle, you’re not alone. Overwhelmed? You should be!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Implementing all these tools and processes at once might be overkill, especially if your project is still in its early stages. But you don’t have to do everything immediately.&lt;/p&gt;
&lt;p&gt;Think of this as a maturity model - a roadmap you can follow as your project grows and your needs evolve.&lt;/p&gt;
&lt;h3 id=&quot;the-maturity-ladder-building-confidence-step-by-step&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-maturity-ladder-building-confidence-step-by-step&quot; aria-label=&quot;the maturity ladder building confidence step by step permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;The Maturity Ladder: Building Confidence Step by Step&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Instead of diving headfirst into the deep end, let’s break this down into manageable stages:&lt;/p&gt;
&lt;h4 id=&quot;stage-1-basic-testing-and-logging&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#stage-1-basic-testing-and-logging&quot; aria-label=&quot;stage 1 basic testing and logging permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Stage 1: Basic Testing and Logging&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;When to Apply&lt;/strong&gt;: This is your starting point, ideal for early-stage projects where the focus is on achieving the core functionality.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What to Do&lt;/strong&gt;: Start with unit and integration tests using the Node.js native test runner. Implement basic logging with Pino to capture key events and errors. This gives you confidence that the system works as intended in simple scenarios. Set up GitHub Actions for CI/CD to automate your tests.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Why Stay Here&lt;/strong&gt;: If your application is small, traffic is low, and the complexity is manageable, there’s no need to over-engineer. Focus on building and refining the core features.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;stage-2-enhanced-testing-and-observability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#stage-2-enhanced-testing-and-observability&quot; aria-label=&quot;stage 2 enhanced testing and observability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Stage 2: Enhanced Testing and Observability&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;When to Apply&lt;/strong&gt;: As the project grows, you’ll likely see more traffic and start adding more features. At this point start integrating OpenTelemetry for basic observability. Use Benchmark.js for performance testing critical paths. This is where you start to see the value in catching issues early before they reach production.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Why Move Here&lt;/strong&gt;: If you’re starting to see performance issues, or if the team is growing and changes are becoming more frequent, enhanced testing and observability are crucial. They help you catch and fix issues early, ensuring your system remains stable and performant.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;stage-3-full-scale-production-readiness&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#stage-3-full-scale-production-readiness&quot; aria-label=&quot;stage 3 full scale production readiness permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Stage 3: Full-Scale Production Readiness&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;When to Apply&lt;/strong&gt;: When your system becomes critical to your business, handling substantial traffic or operating in a distributed environment, it’s time to go all-in.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;What to Do&lt;/strong&gt;: Implement full observability with OpenTelemetry, Prometheus, and Grafana. Automate everything from environment setup to deployment with tools like Terraform and Docker. Regularly perform load and stress tests with k6 to ensure the system can scale and remain stable under pressure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Why Move Here&lt;/strong&gt;: If your system is mature and downtime or performance issues would significantly impact business, this is the stage where you need to ensure absolute reliability and performance. By this point, you want to have a system that’s functional, robust, scalable, and resilient.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion-dont-jump-into-the-deep-end-prematurely&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion-dont-jump-into-the-deep-end-prematurely&quot; aria-label=&quot;conclusion dont jump into the deep end prematurely permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Conclusion: Don’t Jump into the Deep End Prematurely&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;There’s a temptation to over-engineer right from the start - to build complex architectures and set up elaborate pipelines before they’re really needed. But that’s often counterproductive.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start with the basics. Build confidence gradually. As your project grows, scale your processes and tooling to match.&lt;/strong&gt; This way, you’re not just ready for production—you’re ready for production when it truly matters, without wasting time and resources on over-engineering in the early stages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal isn’t to have the most sophisticated setup possible—it’s to have the right setup for your project’s current state.&lt;/strong&gt; By following this approach, you ensure that your system is not just production-ready but also future-ready, capable of scaling and adapting as its demands grow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And hey, dear reader, I’d like to learn where you’d like to go next. Your thoughts mean much to me, and I’d like to make it engaging for you! Please respond to this email, comment, or fill out this short survey: &lt;a href=&quot;https://forms.gle/1k6MS1XBSqRjQu1h8&quot;&gt;https://forms.gle/1k6MS1XBSqRjQu1h8&lt;/a&gt;!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Writing and testing event-driven projections with Emmett, Pongo and PostgreSQL]]></title><description><![CDATA[In the previous article, I told what happened when Emmett and Pongo walked into a bar. In other words, I announced that you can now do Event…]]></description><link>https://event-driven.io/en/emmett_projections_testing/</link><guid isPermaLink="false">https://event-driven.io/en/emmett_projections_testing/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/5566e1eee42e2f76ee638e555fc4d646/a331c/2024-08-14-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4dAAAuHQEHEELWAAADJElEQVR42gEZA+b8AAEBAQEBAQEBAQEBAkdMU5KXnqWmr2dqcgsLDAAAA1lme4+YqoGIm5WguImTqG+CmnGEnm6CnHmLoZiowAARFBUSFRcAAACAiZPj7/e+uLrAnZ7YtK1+c3AADipOZYakscSWpLukscdsg55JdJxPeKFRfKduiqiYp70AnbK/a3yBAAAA1N3h3d3es52cwJqgvIZ34bymTWiNOFuIpLDBmqW4mp+vdoOWTHijUXmiV4Gqd4SVX19pAOX4/46doVNXWvP7/t3U1ceamsmcob2FhN+3oomHkD9IYE5NV0lBREU3O0I5QlprgmaCoEJum2lsdTIvOADW7POnvcS0vsTo9PjUxsXLoKLIm6W9e3G1iGefe16WdVx9aGGBe4F/a2qNbWWhf3WOiYp2jaeReW6ReGwAzd7o9f//4O/31eXx3t7iwJ6fso6RrYJ1opKEh4WEZ1xYmX902sS68cWasntKpYhuhmdLXlRPTTsqUzwtAP3//+36/efw9tfd5un4/6eGfmU+L4qDf46ov217izkkH413ba6QhufJrZV5XxQEABEJBhEGAg8HBSEXEwDx/P7l9vzo8vjg3uHx//+mmJYdGxeIgYaVobKYp7VVREColpK8novWs5bjvJNCLSERCAQgEgscDQYeEgkA7fn85fP75vH44Nvb8/j9tL3ET1BKbmZroKCuqqutiYGEm46OfGhb48KfsH1TZ1JGQj9HQzxCPz5GPjtDAOr5/eLy+eXs8tXS1OTu97e+yIWEjZKXqJGOmJl1bq2LhJF6dI1iRZ1rQoRTMIVoU0RCUktHVEpOX1lWZADj9Prh8Pnl6/DDxM7V4+2jq7+SjJ6Ed4DAq6rFk42QWUprRTiCUDZzPyCXYjCzg1ZINSwwLDR2gJJbWF4A5vT74/L65u/1w8bOvcfVucTXuLrFWkhGpHl1gV5Ubkg/JxUQWzQwiEgilGMspXZIm21JUEA8QEJJFREVAOv4/eTy+dLe6MjT3MnV4cDI0snb6IGEj1E3L4J6drGboZliXoFOTFgfC21BHX9YNlU4JCwgGUBCSEZBRn/8nb2WnHDtAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/5566e1eee42e2f76ee638e555fc4d646/507e6/2024-08-14-cover.webp 200w,
/static/5566e1eee42e2f76ee638e555fc4d646/28a80/2024-08-14-cover.webp 400w,
/static/5566e1eee42e2f76ee638e555fc4d646/8d2ea/2024-08-14-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/5566e1eee42e2f76ee638e555fc4d646/36ca5/2024-08-14-cover.png 200w,
/static/5566e1eee42e2f76ee638e555fc4d646/a3397/2024-08-14-cover.png 400w,
/static/5566e1eee42e2f76ee638e555fc4d646/a331c/2024-08-14-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/5566e1eee42e2f76ee638e555fc4d646/a331c/2024-08-14-cover.png&quot;
            alt=&quot;2024 08 14 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In the &lt;a href=&quot;/en/emmett_postgresql_event_store/&quot;&gt;previous article&lt;/a&gt;, I told what happened when &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt; and &lt;a href=&quot;https://event-driven-io.github.io/Pongo/getting-started.html&quot;&gt;Pongo&lt;/a&gt; walked into a bar. In other words, I announced that you can now do Event Sourcing in Node.js on top of PostgreSQL.&lt;/strong&gt; You can use Emmett as an event store and Pongo, changing PostgreSQL into a document Mongo-like database. With all the strong consistency benefits and integration happening behind the scenes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Let’s explore this in more detail today, focusing on the single-stream projection.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In event sourcing, a stream represents the history of a specific process or object, such as a shopping cart. After each business operation, a new event is appended to the end of the stream. For business logic, we’re getting all events (so facts), interpreting them, and building the current state in memory. That works well for business logic, as streams should be short, and this shouldn’t take long. You can read more about it in &lt;a href=&quot;/en/how_to_get_the_current_entity_state_in_event_sourcing/&quot;&gt;How to get the current entity state from events?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Yet, reading more than one record wouldn’t play well. For that, we need to materialise our data into read models. We can apply our events and store the result in the database table; then, we can apply filtering and get the set of results. Projections are functions that transform events into read models. Check more in &lt;a href=&quot;/en/projections_and_read_models_in_event_driven_architecture/&quot;&gt;Guide to Projections and Read Models in Event-Driven Architecture&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some event stores, like EventStoreDB, separate the event storage and read model storage. That allows its creators to focus on making one thing right. Still, in many cases, it’s more than enough to just use PostgreSQL. That’s what the Emmett and Pongo combination does pretty well!&lt;/p&gt;
&lt;h2 id=&quot;single-stream-projections-with-pongo&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#single-stream-projections-with-pongo&quot; aria-label=&quot;single stream projections with pongo permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Single Stream Projections with Pongo&lt;/h2&gt;
&lt;p&gt;Let’s install Emmett PostgreSQL package:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; @event-driven-io/emmett-postgresql&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Behind the scenes, it’ll also install Emmett and Pongo as peer dependencies, so you don’t need to install them separately.&lt;/p&gt;
&lt;p&gt;Now we’ll define a simplified Shopping Cart flow, expressing it by the events that can happen:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Event&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItemAdded&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; addedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProductItemRemoved&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItemm&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; removedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PricedProductItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  unitPrice&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DiscountApplied&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; percent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; couponId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; discountedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartConfirmed&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Event&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; confirmedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Date &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartEvent&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; 
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ProductItemAdded
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ProductItemRemoved 
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; DiscountApplied
  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ShoppingCartConfirmed&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’ll skip the business logic part; you can learn about it in detail in &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html#commands&quot;&gt;Emmett’s Getting Started Guide&lt;/a&gt;. Let’s go straight to thinking about read models.&lt;/p&gt;
&lt;p&gt;Let’s say that we want to show the summary of the shopping cart, showing just the total items count and amount. This could be used, e.g., in the top menu bar, to give users quick feedback. It could be defined as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartSummary&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  _id&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, let’s define how we’d like to apply those events:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; evolve &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProductItemAdded &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ProductItemRemoved &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; DiscountApplied&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  document &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemRemoved&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;removing&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalAmount &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;percent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; withAdjustedTotals &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  options&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
    document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 
    productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PricedProductItem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 
    by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;removing&apos;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; by &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; plusOrMinus &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; by &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalAmount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
        productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unitPrice &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; plusOrMinus&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItemsCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; plusOrMinus&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; 
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As you see, the transformation may not need to handle all events. We don’t need to know the status (whether it’s confirmed or not); we just need information about totals.&lt;/p&gt;
&lt;p&gt;The next step is to define our Pongo projection. We do it by:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pongoSingleStreamProjection &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett-postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collectionName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shopping_carts_summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCartSummaryProjection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoSingleStreamProjection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  canHandle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemRemoved&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  collectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By that we’re handling the specified range of events, applying it using the evolve function and storing the result in the specified Pongo collection (so the PostgreSQL table with JSONB column for document data).&lt;/p&gt;
&lt;p&gt;If you don’t like getting a null document in the evolve function, then you can also provide the initial state:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCartSummaryProjection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoSingleStreamProjection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  canHandle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemRemoved&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  collectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;initialState&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then your evolve can skip the setup step and look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; evolve &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProductItemAdded &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ProductItemRemoved &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; DiscountApplied&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemRemoved&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;removing&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalAmount &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;percent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Emmett will provide the initial state if the document with id equal to the stream name doesn’t exist.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We need to complete registration by passing it to event store options:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; projection &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getPostgreSQLEventStore &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett-postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;postgresql://dbuser:secretpassword@database.server.com:3211/mydb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; eventStore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPostgreSQLEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  projections&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; projections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;inline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    shoppingCartSummaryProjection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Inline registration means that projections will update your read models in the same transaction as appending events. So, either all was stored or nothing. Of course, you need to be careful with them, as they can slow your appends, but they’re really useful. Async projections will come in future releases.&lt;/p&gt;
&lt;p&gt;Sounds cool; now we can append a few events through regular event store append events api and query results using Pongo:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getPostgreSQLEventStore &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/pongo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;postgresql://dbuser:secretpassword@database.server.com:3211/mydb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongo &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connectionString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCartsSummary &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;shopping_carts_summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; summary &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; shoppingCartsSummary&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shopping_cart-123&apos;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cool, but manually querying data isn’t the best long-term solution. Wouldn’t it be possible to test it automatically? Yes, it would.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That’s why Emmett gives you a built-in way to test projections.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;testing-projections&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#testing-projections&quot; aria-label=&quot;testing projections permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Testing projections&lt;/h2&gt;
&lt;p&gt;In the same way as testing other parts of your development flow (read more in &lt;a href=&quot;/en/testing_event_sourcing_emmett_edition/&quot;&gt;Testing Event Sourcing, Emmett edition&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Before we start, let’s install PostgreSQL Test Containers. We’ll need them soon:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; @testcontainers/postgresql&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As I described in &lt;a href=&quot;/en/testing_event_driven_projections/&quot;&gt;How to test event-driven projections&lt;/a&gt;, projection tests should be tested against the real database. Both querying and update capabilities and serialisation can play tricks, so it is better to be certain that it really works. Tests that don’t give us such assurance are useless. And we don’t want them to be such.&lt;/p&gt;
&lt;p&gt;Now, let’s start with the setup:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; PostgreSQLProjectionSpec &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett-postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
  PostgreSqlContainer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  StartedPostgreSqlContainer&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@testcontainers/postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; after&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; before&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; beforeEach&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; describe&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; it &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;node:test&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; v4 &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; uuid &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;uuid&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping carts summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; postgres&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; StartedPostgreSqlContainer&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; connectionString&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; given&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PostgreSQLProjectionSpec&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ProductItemAdded &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; ProductItemRemoved &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; DiscountApplied&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    postgres &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostgreSqlContainer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; postgres&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getConnectionUri&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    given &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; PostgreSQLProjectionSpec&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      projection&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; shoppingCartShortInfoProjection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;beforeEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;shoppingCart-&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;uuid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re setting up the PostgreSQL test container and projection specification. We’ll use it to run our tests. The first one could look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; expectPongoDocuments &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett-postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping carts summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (...) test setup&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;first added product creates document&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; unitPrice&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shoes&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          metadata&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            streamName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        expectPongoDocuments
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fromCollection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ShoppingCartSummary&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;shopping_carts_summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            appliedDiscounts&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Tests are written in &lt;a href=&quot;/en/behaviour_driven_design_is_not_about_tests/&quot;&gt;Behaviour-Driven Design&lt;/a&gt; style, using Given/When/Then:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Given&lt;/strong&gt; existing stream of events,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When&lt;/strong&gt; new events are added,&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Then&lt;/strong&gt; read model is updated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We do it this way, as we expect read models to be ONLY updated through upcoming events.&lt;/p&gt;
&lt;p&gt;Emmett gives you also out-of-the-box test assertion helpers to make testing Pongo easier.&lt;/p&gt;
&lt;p&gt;You may have noticed that our Given is empty, so let’s fix it!&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  eventsInStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  newEventsInStream&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett-postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping carts summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (...) test setup&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;applies discount for existing shopping cart with product&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; couponId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uuid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;eventsInStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; unitPrice&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shoes&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;newEventsInStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; percent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; couponId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        expectPongoDocuments
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fromCollection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ShoppingCartShortInfo&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            shoppingCartShortInfoCollectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You probably noticed the next helpers: _eventsInStream_and &lt;em&gt;newEventsInStream&lt;/em&gt;. They’re responsible for shortening the setup. Depending on your preferences, you can use the raw events setup, including manual metadata assignment, or a more explicit intention helper. My preference would be to use the helper, but it’s up to you to decide!&lt;/p&gt;
&lt;p&gt;You could even write tests like this:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping carts summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (...) test setup&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;applies discount for existing shopping cart with product&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; 
    &lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;shoppingCartWithProductItem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          unitPrice&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
          quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;discountApplied&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          percent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;summaryUpdated&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The setup methods are defined below:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping carts summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (...) test&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;shoppingCartWithProductItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
      productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Partial&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;PricedProductItem&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; 
    &lt;span class=&quot;token function&quot;&gt;eventsInStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; 
            unitPrice&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
            productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shoes&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
            quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;productItem
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;discountApplied&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; 
    data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; discount&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; couponId&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; 
    &lt;span class=&quot;token function&quot;&gt;newEventsInStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; percent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; couponId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uuid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;data &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;summaryUpdated&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; expected&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt;
    expectPongoDocuments
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fromCollection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ShoppingCartShortInfo&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        shoppingCartShortInfoCollectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toBeEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;     
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That approach is more focused on the business flow and allows us to reuse those test setups. Still, the outcome is the same.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can read model data be only updated? Not only that, you can also delete it.&lt;/strong&gt; Let’s imagine that the confirming cart should clear its read model, as we expect a new, empty shopping cart to be created when the new buying process starts.&lt;/p&gt;
&lt;p&gt;To have that, we need to update our evolve function by adding &lt;em&gt;ShopingCartConfirmed&lt;/em&gt; event:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; evolve &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartEvent&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartSummary &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;= see here&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;adding&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemRemoved&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;withAdjustedTotals&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        by&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;removing&apos;&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalAmount &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;percent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;= and here&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We made the shopping cart confirmed event return null. That means that the document should be removed. Emmett is using the Pongo’s &lt;em&gt;handle&lt;/em&gt; method internally:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Document&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;collectionName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDocumentId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;evolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pongo’s handle method loads the existing document and tries to insert, replace, or delete it depending on the function’s result. It’s a bit sneaky, but pretty useful, isn’t it?&lt;/p&gt;
&lt;p&gt;The test checking will look as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ts&quot;&gt;&lt;pre class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;Shopping carts summary&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; given&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PostgreSQLProjectionSpec&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ShoppingCartEvent&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// (...) test setup&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;confirmed event removes read mode for shopping cart with applied discount&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; couponId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uuid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;given&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;eventsInStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            productItem&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; unitPrice&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; productId&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shoes&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; quantity&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; percent&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; couponId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token function&quot;&gt;newEventsInStream&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            type&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ShoppingCartConfirmed&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; confirmedAt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        expectPongoDocuments
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;fromCollection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;ShoppingCartShortInfo&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
            shoppingCartShortInfoCollectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;withId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shoppingCartId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;          
          &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notToExist&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;= see this&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The pattern looks the same, but the assertion is different.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I hope that this article shows you how powerful the combination of Emmett, Pongo, and PostgreSQL is.&lt;/strong&gt; I also wanted to show you that I intend to give you certainty and trust in the software you’re building. Having built-in support for tests should help you with that.&lt;/p&gt;
&lt;p&gt;Go ahead, play with it, check it, and drop me a line. &lt;a href=&quot;https://discord.gg/fTpqUTMmVa&quot;&gt;Joining our Discord&lt;/a&gt; is an excellent way to do this.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Architecture Weekly #190 - Queuing, Backpressure, Single Writer and other useful patterns for managing concurrency]]></title><description><![CDATA[Welcome to the new week! In the last edition, we explored why connection pooling is crucial for using databases efficiently. Now, let’s take…]]></description><link>https://event-driven.io/en/architecture-weekly-190-queuing-backpressure/</link><guid isPermaLink="false">https://event-driven.io/en/architecture-weekly-190-queuing-backpressure/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In &lt;a href=&quot;/en/architecture-weekly-189-mastering/&quot;&gt;the last edition&lt;/a&gt;, we explored why connection pooling is crucial for using databases efficiently.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now, let’s take a step further and perform a thought experiment: How would you actually implement a connection pool?&lt;/strong&gt; We’ll use Node.js and TypeScript as an illustration, as we need to choose a specific stack to make the concepts concrete.&lt;/p&gt;
&lt;p&gt;If you think, “Well, I won’t ever do it myself”, don’t worry—the intention here isn’t to drown you in the imaginary code. Instead, I will show you how these micro-scale coding considerations can impact macro-scale design analysis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As we go, we’ll learn a few valuable concepts, like Queuing, Backpressure and single Writer, and discuss why queuing can be useful in managing concurrency and beyond!&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;connection-pool&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#connection-pool&quot; aria-label=&quot;connection pool permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Connection Pool&lt;/h3&gt;
&lt;p&gt;Let’s start where we ended, so with our database access code:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/28be39baf0afc11274f20fa18072c49a/171d2/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 118.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAYAAAD6S912AAAACXBIWXMAAAsTAAALEwEAmpwYAAADaUlEQVR42p1VS47jNhD1ZWJbEqkP/6Q+trunG4PcYIDkKkEQZDGLuczAwJytARGovJLc3VrEjcEsHli0xceqelXFnWn8H4/BfasG889RmH9/BYXE2cp8Kyrz527s0vXLc0/ys8tFHaioPZWNx+qoZBuomvAh8E2uVU9VHX7sjsJ91+qZOjm9NMrP1oVZNH7W7WWuVZxr62fcPh8q/RFe2IGD0NddId3V6DMJvqW1JBsHO1ADSL4dexy4wSw4MsQ78F/mqI6V/rErQejsE3VmzC4hdDOSsZ/I6YGcG0lqhNU5qtoVfEFZ2/uEh7K7inCmenrKKp2ochOVICrNQEI5UjGRSokaF0liL5RfSD/0UIULRLC5kor2ZQeoGzT9VjDW/RI2fmP7LiHnsO1GKiqXOXeVBJDgGmE2nadWe3gG77q4KF/hoIB9lPYOofDXPjyT0WP2yGUKn8kgbKiNHAYKMZL2J+rstJRTrRK1sJnozestIUK6RohxPqWsPITwE0Xkr5LwEsI0ZlpJdE8lPBOwlR2pc6flohY2yDLX61vICn8O41P26YHS8EgxXkj5Mw5OCDsifIQMwqoNCzh8cbN5LaR9J9yjGI25kDdQ2SYKUJTLRHUgbU+E1iTJh5AGgQtqEL/X5YrjNmQmZFGMuuSmHaiue2qagTqUhoEnLcg00ABbZe/XIQgl8uL1Yx7s7/D0ESQDtVCUiWp82LKX6JqtsncJ2UPZRj6cA4glJzmcSCKH/NEeIe1fQ/sZD1mURqMOhcklPECC0Vpuqbli2Zs3/DTh6kmX2Qvujv123dhbIbZ71OOi8mFtPX+NFoXdPWSDwTAOiXr0b58G6vuRhr6naRwocj+jPm18wCDpUVoJKxf9mWx6yPwfhsaaQ61PlMynHLuJegyGiBIZ8EEPGAjTYeIEz8MhUQFxkJ4Fm1GWuSWPYlHZXNldlElOEeUyXqibHqi2kapluqwjqzZhGV+iW8H7xqLINfe2y7WOPDhWwpsAeezhqUs0+Z5C6G9hT8tMbNlDHGYiiRHGZLVdbSbkAbLkEG5+Z1Ew9l+EsHMFvK5laeaSx3+pZ4ywGeLMe163KNX6BMjbE4C2urYQAwnNRXN7oJr1cSpv+6p5faj+78Hym0fKwcNSfzlU6isS+xdu+vtXsJ41X5nrP98U0BeXgWXZAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/28be39baf0afc11274f20fa18072c49a/507e6/image-2.webp 200w,
/static/28be39baf0afc11274f20fa18072c49a/28a80/image-2.webp 400w,
/static/28be39baf0afc11274f20fa18072c49a/8d2ea/image-2.webp 800w,
/static/28be39baf0afc11274f20fa18072c49a/71c32/image-2.webp 1106w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/28be39baf0afc11274f20fa18072c49a/36ca5/image-2.png 200w,
/static/28be39baf0afc11274f20fa18072c49a/a3397/image-2.png 400w,
/static/28be39baf0afc11274f20fa18072c49a/a331c/image-2.png 800w,
/static/28be39baf0afc11274f20fa18072c49a/171d2/image-2.png 1106w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/28be39baf0afc11274f20fa18072c49a/a331c/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;As we discussed in detail in the previous article, a connection pool manages reusable database connections shared among multiple requests. Instead of opening and closing a connection for each request, we borrow a connection from the pool and return it after database operation.&lt;/p&gt;
&lt;p&gt;Thanks to that, we’re cutting the latency required to establish a connection and get a more resilient and scalable solution (&lt;a href=&quot;https://www.architecture-weekly.com/i/147364154/when-proxy-connection-pool-might-not-be-enough&quot;&gt;at least if we’re not into serverless&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Stop for a moment, take a sheet of paper and pen, and try to outline how you would implement such a connection pool.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Done? Now, think about the potential limitations of your thinking and the potential solutions to them.&lt;/p&gt;
&lt;p&gt;Ready? Let’s compare that with my thoughts!&lt;/p&gt;
&lt;h3 id=&quot;basic-definition&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#basic-definition&quot; aria-label=&quot;basic definition permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Basic definition&lt;/h3&gt;
&lt;p&gt;Let’s start by defining the basic API. It could look like this:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7585d887e84bde3a6f74f095f6a55ef6/8df04/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 149.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAACXBIWXMAAAsTAAALEwEAmpwYAAADc0lEQVR42p1W247bRgz1x7S2NTdprpJ18SXpbrfJt2w/qUnf2iKo9vMS7ADsoeQ0sDfG2nk44MiYoXnIQ84sdiY+DkP456fgPywL9+dSfMNKhauwVuHDSoa/Vyr+vmhsO26HgVRosi5rMrYmbdluSFUNCfz2GgqTsrItCVM/LdYqfQrtO4r13Ze46XM3HHLbbbPzdVbYuJb+GnwROlEh/b+LtQ6jqrdUhC6LEhFVGzK+J1AAPIHO64BTRAcbnhbS1KPWDXvPQkcS2FDogPATU6E1frvNoa7HNPxGfv+Q4+6B/Pae0nBP3f6BqjjAebrNIVOWCZTTkIVrqUAhlO9IY30V3e9RNmZDQoYsTSAFumyZNufwqjyeUo5j6t9T2Nzn1AyUuh0N+7fUDm/IIfIqdHAeb6Qce1S3zmvQ1agwpEQQKU1FMjfmUDPlsuXDoJyIoViw1zi6VOXQ/UpuuMsAeVQ49r9Q09+RcR0OxB+schhyEXoq4ISFXU7UPS2lA33/f4G+W6TzKpdVD5oxmyoRQ0+IpLCJ06FMM/U1p0Khk15o8ySHVTt6vyeL3vWxIRsackdUVUeu2sEpooYuGSVYyDKdRnpO2YYDVXafRdySTli7gUK9m6ZOYSLJqp7AUcpytidSOnEo/ajrPSGPWUE+Ph7Ihz2mT0chITJ7jAzdUyGvFSLk74sOEfpowo58epu921FC/8bNlhzggTJiLjrk0855w+B9WZhzyorHloxZQCISmwvehPYTZZzyJWEF7MWOeRFhfWCZZNvsSfDk5fGFw/NM/IardVi6niuZJegK1uFRPqsf6RQsRr4XfhYur475WXJEyk/Cvtmhrpox+APF2OS63qCyrMF60l+wSAVEbxG5jTx5BkhpMxflkg6FiaMCZWE3WbI8IOYSQrYewnbNJBGWCsvGACxstqzFi0VRELEMfebRVVm0IQbu1xaUUICertT5WuU142QanTtkKo1rs2NK9UBVjbsZrWfQimXiGYki4dtaFA1/KsIMHioT4pDLyMPkqw5BQdgmc1vxXTLdeDqcIUKfcdbiS2Q5p+BpsVT+U8md0tx91s3huWzePMt6/yzi8Kxc+wwGrwKUP/MEWvFFr/EUMbbj7shzZImfFPMzw1wHnMmqOj5F0G6P/NBB2H/AfoTjj2xvwVrirIh/4R56/A+vyoZ6nUV3hAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/7585d887e84bde3a6f74f095f6a55ef6/507e6/image-3.webp 200w,
/static/7585d887e84bde3a6f74f095f6a55ef6/28a80/image-3.webp 400w,
/static/7585d887e84bde3a6f74f095f6a55ef6/8d2ea/image-3.webp 800w,
/static/7585d887e84bde3a6f74f095f6a55ef6/68fc1/image-3.webp 1200w,
/static/7585d887e84bde3a6f74f095f6a55ef6/4af63/image-3.webp 1416w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/7585d887e84bde3a6f74f095f6a55ef6/36ca5/image-3.png 200w,
/static/7585d887e84bde3a6f74f095f6a55ef6/a3397/image-3.png 400w,
/static/7585d887e84bde3a6f74f095f6a55ef6/a331c/image-3.png 800w,
/static/7585d887e84bde3a6f74f095f6a55ef6/8537d/image-3.png 1200w,
/static/7585d887e84bde3a6f74f095f6a55ef6/8df04/image-3.png 1416w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/7585d887e84bde3a6f74f095f6a55ef6/a331c/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There is not much to explain besides that we want to manage the pool to the database represented by the connection string and limit the number of open connections. Why?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Imagine a service like a payment gateway that handles transactions for a popular online retailer. During peak shopping times, such as Black Friday, the system experiences a huge increase in payment requests. Each transaction requires a secure connection to the database to verify payment details and update the order status.&lt;/p&gt;
&lt;p&gt;Yet, the database has its limits, so we might need to try somehow to squeeze that into the limit of 100 concurrence connections. When all 100 connections are occupied processing transactions, new incoming requests must be managed effectively.&lt;/p&gt;
&lt;p&gt;We could try to:&lt;/p&gt;
&lt;p&gt;- &lt;strong&gt;Fail Immediately&lt;/strong&gt;: The system could reject new requests outright, leading to a poor user experience and lost sales.&lt;/p&gt;
&lt;p&gt;- &lt;strong&gt;Retry Aggressively&lt;/strong&gt;: The requests might enter a loop, repeatedly attempting to acquire a connection, which can further strain the system.&lt;/p&gt;
&lt;p&gt;The naive implementation of a fail-fast strategy could look as such:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a675d567f4297d1d07735527707f4019/47e9c/image-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 130.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAaCAYAAAC3g3x9AAAACXBIWXMAAAsTAAALEwEAmpwYAAADt0lEQVR42p1V2XLjNhDUxyTiARA3AVLUrbUd/8fWflZcrrzl+D+nwKpJDyR5ZW0qtZuHFkAKGs109wwWveg/nzbhj2bwLz9V4XXZfkVVVldQ/QsaGa54qbvwe935z4ukV6/Ppz3Z7ZiNW5EJgB/JYtUesGuydkPKTqTcGdqvqcNZoVNBq+LMz61Or4tGxZdO78iY9BZjmr2PswNCiLO1gN7OwR5mracZgQps3MzKjnMtfEEl/N9tFwn7l0WFlPv+QMk9ZOl31I5HksORFCDTnoTlLAIthSP88ILzvpahAPu5VYk4Vgno3I6C2efOrUkMO5JhTS5uSYcNNV2PH/mbYP5DsG8DglzhJ2rClFtww5w5tSWjR+r7CD4HMv2m/IHF6pG1T7vyrrPjJehdwA6B2rDJLQ7wIeM3EGcD8ifqzIgswY/sqQFqEc77kvkly48lI0O7omF4zmN6Ijc9Utr9QnE8kIlQFGpL5tFGqNmTdJGESf9RMj4gOykN22hkpWETPGsdqfOA4zWVvdA96T7BOgM1omRWUALqmwylHkiomFn6ClkszUAVSq3VACAb0ZOQeMa5ip+xNqCmwbmmUNLfZYjowR3y2D+S64/Ug/SwOsDsMPy0I5smisMeVoIw632BXu1IjVtyG5wb1rNE4PeAF7dn0SWSDJQhYBX2H0OanloTkEkoZbeX9y325TsdZsHZXwNyyeAulzKgbAsfsoUaiNVwKYWrqwBfUb/v722DH47hKftwJIaaPpFafyI5csfsi5WqG1Xv8Y3KXP9Suly3yKR1RbnlXTfU3x0QGaoIEdJDduw9ZNWAUw3vfTDvjwSUKIl92MIGbeENPjTcGRev/VBAfHD/KjVmwZ5iYTDbaqyCffYdWVb3rWcwQEf/mJ0/oPGPlEBB328pQm1vy+As4+vaGTcdUrj+GQG5qvcMOQsZ99nsnmHYBwrTkVbbI00IHMcTGfS3TSfqoLzaPABP2D9QF85tanSaNaq6cMgqDyQ9xheXasbS/Ab9qywPAkBhCqlVoYPPFJ6xZ+4tC6jjrO3qVpQVl5Cr1hbb4B0tYSHGh6HK6+X7gmvJrbst+cxhMIfMc1FgkEr0skS5ggdrv6IYR3JhKPPR4T2jzEuUrBxfZtPMAnK1C/zDC99o0Z/eJIasSPuMOwU4ZBHxbFJuVcAkChmKZvCdue95ZVzevXGGiPXrAjy8GiiK4ZkFpL9Hiz6/QhSkC4Z3wDK4RqfzNYoL+ksl/V98YYOj3/4Pym+l/7Pu3Jd/AEwoDW1JVXOuAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/a675d567f4297d1d07735527707f4019/507e6/image-4.webp 200w,
/static/a675d567f4297d1d07735527707f4019/28a80/image-4.webp 400w,
/static/a675d567f4297d1d07735527707f4019/8d2ea/image-4.webp 800w,
/static/a675d567f4297d1d07735527707f4019/68fc1/image-4.webp 1200w,
/static/a675d567f4297d1d07735527707f4019/a0d74/image-4.webp 1296w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/a675d567f4297d1d07735527707f4019/36ca5/image-4.png 200w,
/static/a675d567f4297d1d07735527707f4019/a3397/image-4.png 400w,
/static/a675d567f4297d1d07735527707f4019/a331c/image-4.png 800w,
/static/a675d567f4297d1d07735527707f4019/8537d/image-4.png 1200w,
/static/a675d567f4297d1d07735527707f4019/47e9c/image-4.png 1296w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/a675d567f4297d1d07735527707f4019/a331c/image-4.png&quot;
            alt=&quot;image 4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now, While doing retries in the loop or even immediate failure can be a temporary solution, for obvious reasons, it won’t scale and will result in a subpar experience.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Consider a busy restaurant with limited seating. When all tables are occupied, you can be asked to wait a bit near the entrance. As tables become available, the waiter will invite you to be seated. This ensures that the restaurant operates smoothly without overcrowding or turning away customers.&lt;/p&gt;
&lt;p&gt;We could do the same with our connection pool implementation. Instead of rejecting new requests or letting them endlessly retry (which could crash your system), they’re placed in a queue and handled sequentially.&lt;/p&gt;
&lt;p&gt;Queuing brings the following benefits:&lt;/p&gt;
&lt;p&gt;1. &lt;strong&gt;Sequential Processing:&lt;/strong&gt; Requests are handled on a first-come, first-served basis, ensuring fairness.&lt;/p&gt;
&lt;p&gt;2. &lt;strong&gt;System Stability&lt;/strong&gt;: Queuing prevents the system from being overwhelmed by controlling the rate at which requests are processed.&lt;/p&gt;
&lt;p&gt;3. &lt;strong&gt;Enhanced User Experience:&lt;/strong&gt; While users might wait longer, they receive feedback that their request is in progress, often preferable to immediate failures.&lt;/p&gt;
&lt;p&gt;Okay, but how do we add queuing to our pooling? Let’s start by defining a simple queue. Essentially, you could think about it as an array of pending items.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 692px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/480cfd5b7c6acdf8221dbbe3c046f5d2/607b4/image-5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 70.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB7UlEQVR42qWTzXLTMBSF8wasCossmOkE2/rzr2zLjuPEkKZpBmibMMOS93+EwkiXoyQNLQy0HRbfxJauj849NxpJWb8Zi2z36jzbjsd6K97qbSReiCxvQ159ieP49Wgs9G6jZ/Ttg7Hr29JNrgwJURFPQPxsrEwNMVndjLy7Zd7S13lph03uzpcVRaygUAJxIJL6KawXZrK4GQk4DFVN70RjI944qQzJrCEF/KkCRCgWcY0Py6cFJ3CYZz0N5pOtisGZ+YrafkXNEW0GYhCN0cWzHIax3vlFXkyt6ldOdUtS0yXJfAqXLfGyJ171VJoFtfM1JUVHAc8p8lEcIwl4Zpn6Q7CzyXzt4v6SZD2ntJhSD7Fed1RBMC5nZHCY6S5I1wuSGKTAmkatmV1YH1PAvCAy9O3w1FiWt44fnpFZRUlSU4pf6dsBap9tS8pP1IN9v5agO4HnYO8QGSqIMFXakOcOnKYbHDm9Yy847j9uObfslCEcQsxvWLTufLgPA79/34eOOs/vNX+Z8rWt9dI1mHL//iMVzUAc7XSLNXXDBpO/pCmG0i2uqEBuvsUH/9FfgpHI/NXBydUd3H5H0Y9/gY/3PFqX+u7gvrge+bvIcRqLS5+DY8cBvBAr7q/eJO3OAiiHSn/+HwKITdL07Cf/foj+lLA7cQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/480cfd5b7c6acdf8221dbbe3c046f5d2/507e6/image-5.webp 200w,
/static/480cfd5b7c6acdf8221dbbe3c046f5d2/28a80/image-5.webp 400w,
/static/480cfd5b7c6acdf8221dbbe3c046f5d2/e0f44/image-5.webp 692w&quot;
              sizes=&quot;(max-width: 692px) 100vw, 692px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/480cfd5b7c6acdf8221dbbe3c046f5d2/36ca5/image-5.png 200w,
/static/480cfd5b7c6acdf8221dbbe3c046f5d2/a3397/image-5.png 400w,
/static/480cfd5b7c6acdf8221dbbe3c046f5d2/607b4/image-5.png 692w&quot;
            sizes=&quot;(max-width: 692px) 100vw, 692px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/480cfd5b7c6acdf8221dbbe3c046f5d2/607b4/image-5.png&quot;
            alt=&quot;image 5&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Instead of throwing an error, we will enqueue an “open connection task” to the queue and return Promise. That will make the user wait until the connection is open.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b2ffdf935763c763e621916358723468/b6207/image-6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 83.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAACc0lEQVR42pVU23LTMBDN1zS27rIkW3actDTNAzzyGTADHwLMQOEFfoaS/hTFzixHslM6pWXow8muZelk9+yRF176V9tN+HLSuHdL7j8s5dNRSP8e8TPi60Vv2v3LFysq+mZkMhLTkUrVEEdkdyLTNfJmymekdxOag7Rdyq8XpYrftT6nysafbYxDCEAdBx+aocGzMd0Qqosh2O2gTT/Yej0Yvx6q+gyxH5bcDUvhbkoZCPnVgqmw9/6MjOlHbVckfEfKtWRcR8I0JG1LTNYZpQiUDjJVZ6S8EB5wh/QMya4XYN1ruyat+5Gnsn2fSTQiR0x5KX1GcUtwH/cIRViTas9H4zcgWpNb70jXp+QAi3c2bAhtovIexOmgS4dpyXzO/yLkKlLtt+MqPCffPKOuvyDXnJIAoXQrkKPi0JLyETGCGFJUDRmsJ3lQZSZEnAmhFfTAlDE94UiiDZbawwSLpJf2xA200xNKBSByW+ccHIdpKEdCHFS6GwUqlRpDAYkGpMF6hedqhbzFkABoalCdApnAHoscBR0E9k0VCrdPnqqr7Rj9jly3o4C2Y7fNGmq0nDTU0E8hV5i+Q+vWoX2QOUgANxw4/jQT4mc/9z8qaCcAiUGwRIRqapjZwU7WbIiDmENbVm8yFAY1D+nA5DyUIyHcPqZpsyqJ7B6xx4RyxuO2Mdl3Y97E/032Xz5M9zC1XPB5wx3S2WdPIrxKU8YtuMnWUfV4jAlJCgZw1eR17H8A/td8l38s0O4+3QKQjuJojVuLdDAwgJjytPbnCxMf/tosRXgD5m+FCB/R8mXBq8uTHH2OJ/l5QjGvP4BPkOkrKn37G+8s+lvpcpAlAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/b2ffdf935763c763e621916358723468/507e6/image-6.webp 200w,
/static/b2ffdf935763c763e621916358723468/28a80/image-6.webp 400w,
/static/b2ffdf935763c763e621916358723468/8d2ea/image-6.webp 800w,
/static/b2ffdf935763c763e621916358723468/68fc1/image-6.webp 1200w,
/static/b2ffdf935763c763e621916358723468/7fc03/image-6.webp 1210w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/b2ffdf935763c763e621916358723468/36ca5/image-6.png 200w,
/static/b2ffdf935763c763e621916358723468/a3397/image-6.png 400w,
/static/b2ffdf935763c763e621916358723468/a331c/image-6.png 800w,
/static/b2ffdf935763c763e621916358723468/8537d/image-6.png 1200w,
/static/b2ffdf935763c763e621916358723468/b6207/image-6.png 1210w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/b2ffdf935763c763e621916358723468/a331c/image-6.png&quot;
            alt=&quot;image 6&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In other words, we’re telling the pool user that they can safely wait and when they can stop waiting (thanks to the native Promise handling, in .NET, that’d be &lt;em&gt;Task&lt;/em&gt;; in Java: &lt;em&gt;CompletableFuture&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Now, we’ll need to trigger processing, and we can trigger it immediately once the pool is created. We should also be able to stop processing; we can do it by:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c1315de8cbf9c2bd701df9a0949c66ad/4c281/image-7.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 150.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEH0lEQVR42pVV227jNhT0zyS68k6KsqTYcZwELdCvKBbYflTrZF8WC7Rv+3O7oBencyg7cZzkIQ8Tkoo1mjPnwoWpu8+3V/57MdivF6X7dlED7WHNe5tRZLhvBZ7l9QX8V6zfgb8WQQ67P27XpFbLJM2SlFvScVV2SUIPpMyEZyMJM5C0R4zUqB6IVMsu5bOMj4tShL+V3pDR/Y+u6/c+9HvjY14tVqWmvbebvCo77pUb99pj76Z92fp90TjGD5DSZeN2ixJ/VFxT1a1S6aHErcnpNWk9UYg9Od+RgFITrsh4IKzJdKt8bnVPZesIHImVFo1/XDArh6J1n6CSDPaNPoSqR6oRXsPhuIlaw+FHhA8iEUDmM4pXhCDQMiQL2RqoVAdv4KUCoQcZlDdQ18BbbToQ5pffJuSQW3y9VDHVeMgq+J9zOPhxbZ/ROPYpr0ey1woruxP4eoh3KYYt+emezOqeNPuKsKqT0N7DS4Ui7Gq8JOWQvNmQQ3n08E0BNcyXMJ9/XJ6pep8QIXM9KTmmhkmgrES42sJH+HlR++xXkUP9AKHRVyjsCUQoDR3Jg1RZPAex1JzZDufIXs82NLlcCF3CvqZaRt7PhHxwZpNQwKixG3JhRT5ek13CgmFNNuBDYaC+G8m7ER9F5vFBhsMHDBJq8E4m5LJpuRz8lLNcalbRZyUlfxUoRUcFVLHXAmAruDMqQAAtSo5r+YmQ65B9KOaqf4l2TkZ18K84nIv3Qr5o7M65axr8b8naNUW3pRbtJaZbEsOWdHeN9kPY45ZMDxuGmzwcGuGffaztnJT2KSmRgr1BHd5S192R7DckUYstPFTxBqQb0j3IsRd4Lnv0Oshb+FZzF7kxcSM8ZXmW61JAUjp3hzE1UaG4BWNGATV1y97hjBePqOE9o9IxcaW8IGxEn7h3lYXxuuO6pNZOVCOrNQ8GZPa0XE7xKuRMKPukMLLUoRxaNeRhIPjMSljVO234qrDrnPo+e+jjPYkr+LT5nVokIMJHi/YrPtJ6tTp4iPJReLlC+0n0cfZWzjX4AcKw4wJFuElrtJ1ZEa8NDwWQ8mB13IYfCpmz2QYoHMii7gTaznVbCn6VW83HkdwSCes4gp6kn2F6vswGJj1NSsgeSj0khUTwaL88dEPJU6aeu6EUJzg7s10vQmbCRsQU/S1CxiWEbEfcGx796aHKQVUNf3kCRWTcAwH/syCxDghDaub2fR5fDV8BGAKMJiPMkDOqw3BozlBDYSX8c2Efb70SHhaH+4IHwOXTUD1BHg7HIXGCs6T80+QLKfyEil8I/9dxZeCHT3sQvQmQ/pyvCf+wwGzbaVxSqLksm2djXuEJg9Uf91yXb6ESHebhyPPxcQH2P6HuX8y9B+y/oC+/5PUIPp8/OwNUPuD9/2Dfp/8Bx26GLJP8ZOoAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/c1315de8cbf9c2bd701df9a0949c66ad/507e6/image-7.webp 200w,
/static/c1315de8cbf9c2bd701df9a0949c66ad/28a80/image-7.webp 400w,
/static/c1315de8cbf9c2bd701df9a0949c66ad/8d2ea/image-7.webp 800w,
/static/c1315de8cbf9c2bd701df9a0949c66ad/68fc1/image-7.webp 1200w,
/static/c1315de8cbf9c2bd701df9a0949c66ad/7408f/image-7.webp 1262w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/c1315de8cbf9c2bd701df9a0949c66ad/36ca5/image-7.png 200w,
/static/c1315de8cbf9c2bd701df9a0949c66ad/a3397/image-7.png 400w,
/static/c1315de8cbf9c2bd701df9a0949c66ad/a331c/image-7.png 800w,
/static/c1315de8cbf9c2bd701df9a0949c66ad/8537d/image-7.png 1200w,
/static/c1315de8cbf9c2bd701df9a0949c66ad/4c281/image-7.png 1262w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/c1315de8cbf9c2bd701df9a0949c66ad/a331c/image-7.png&quot;
            alt=&quot;image 7&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Queue processing happens in the &lt;em&gt;processQueue&lt;/em&gt; method. It’s an asynchronous method that tries to process the queue until all items are handled. We’re triggering it immediately upon pool creation. It runs until the pool ends, or there are pending items to process.&lt;/p&gt;
&lt;p&gt;We also added handling to make a graceful pool ending accordingly. When that happens, we must reject all pending items and wait until that’s done. We’ll know that as we’re enqueuing the “end all processing” as the last item in the queue.&lt;/p&gt;
&lt;p&gt;Cool, aye?&lt;/p&gt;
&lt;h3 id=&quot;limitations-of-raw-queuing&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#limitations-of-raw-queuing&quot; aria-label=&quot;limitations of raw queuing permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Limitations of Raw Queuing&lt;/h3&gt;
&lt;p&gt;While queuing addresses the immediate challenge of handling excess requests, it introduces its own set of challenges:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Unbounded Growth:&lt;/strong&gt; The queue can grow indefinitely if incoming requests arrive faster than they can be processed. This can consume significant memory, leading to performance issues or even system crashes. In our case, as we’re only processing the queue (creating a new connection) when we are below the maximum number of active connections, that can quickly escalate if our connections are used too long.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Increased Latency:&lt;/strong&gt; As the queue grows, each request’s waiting time increases, leading to higher response times and potential timeouts. You saw that even in raw code, the initial version just returned a new connection; now we’re returning the promise. Promise that will be fulfilled eventually (or not…).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resource Consumption&lt;/strong&gt;: Each queued request might consume resources (like memory for request data), straining the system as the queue grows.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Returning to our payment gateway example, if the rate of incoming payment requests is consistently higher than the rate at which transactions can be processed, the queue will grow over time. This leads to longer wait times for users, increased latency, and potentially even lost transactions if the system becomes overwhelmed.&lt;/p&gt;
&lt;p&gt;To mitigate the pitfalls of unbounded queuing, we introduce the concept of backpressure.&lt;/p&gt;
&lt;h3 id=&quot;introducing-backpressure-controlling-the-flow&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#introducing-backpressure-controlling-the-flow&quot; aria-label=&quot;introducing backpressure controlling the flow permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Introducing Backpressure: Controlling the Flow&lt;/h3&gt;
&lt;p&gt;Backpressure is a mechanism by which a system signals to upstream components (like clients or calling services) to slow down the rate of incoming requests. It’s like a network flow control mechanism, where data transfer rates are adjusted to prevent packet loss or congestion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How Backpressure Helps:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Preventing Overload:&lt;/strong&gt; By controlling the rate of incoming requests, the system ensures it processes tasks at a sustainable pace.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resource Management:&lt;/strong&gt; It prevents excessive memory or CPU consumption caused by an unbounded number of queued requests.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Improved User Experience:&lt;/strong&gt; Instead of allowing requests to languish in a long queue, the system can provide immediate feedback to clients, prompting them to retry later or handle the slowdown gracefully.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;How to actually implement Backpressure? The most popular options are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rejecting Excess Requests:&lt;/strong&gt; When the queue reaches a certain length, the system can start rejecting new requests with appropriate error messages, signaling clients to retry after some time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dynamic Rate Limiting:&lt;/strong&gt; Adjust the rate at which new requests are accepted based on the current system load.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Client-Side Adjustments:&lt;/strong&gt; Clients can be designed to handle backpressure signals, employing strategies like exponential backoff to retry requests.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s apply the basic option, so rejecting Excess Requests. We need to extend Connection Pool options to provide max queue size:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/680e2e8150bc0b79075b92f49d8adc86/2e4b5/image-8.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 46.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABj0lEQVR42o2Sy27bMBBF/TWWxJdkkxy9bNmOkjpIt+2639CH/qNF0aKLoovkH5PANDC9VOQm9SqLg5khics7Q84y7fuyKG+pcj9T736l0k+4V5Mp+yOV9m4u6GKW62r4sNny+3cUkrc1C1myyJ/IDOILZH6i+g9hqkNu1/HM51kil1900bJU1aPUdNRFdZSGjiovx1zFOoI61f6YYS+R9pyHaCAV9lMUHLzrmWgfbHvJtL5it4rxDZfdfqzLtud2c82FW7Na1IwxcarcPxLpDriUE2WjQzdEddn0Qa2uWDU9awho5LK5YEMdE9ZqXLDwa56LJQTsmaA9xNYT5ceWh6huuutg4MJs9hC8ZBOBWA2Hq90NL2nDqXYcz8Y5CkMvKA960UwOoRoLSd2DrHYBEWyDsG3A/IKH8xx5pgn4oBd1gGiAaIDYGFXR3EeNubAfZ7A6FL7jTLkgMJsRFSPhlWlsCWLTS9OYZ+a5ntbgsD29st0lYvkb7XxLtPt+TjaBWT9x2pPPpMp+xT/8A63tX9fvAaqYj+W7AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/680e2e8150bc0b79075b92f49d8adc86/507e6/image-8.webp 200w,
/static/680e2e8150bc0b79075b92f49d8adc86/28a80/image-8.webp 400w,
/static/680e2e8150bc0b79075b92f49d8adc86/8d2ea/image-8.webp 800w,
/static/680e2e8150bc0b79075b92f49d8adc86/344e2/image-8.webp 882w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/680e2e8150bc0b79075b92f49d8adc86/36ca5/image-8.png 200w,
/static/680e2e8150bc0b79075b92f49d8adc86/a3397/image-8.png 400w,
/static/680e2e8150bc0b79075b92f49d8adc86/a331c/image-8.png 800w,
/static/680e2e8150bc0b79075b92f49d8adc86/2e4b5/image-8.png 882w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/680e2e8150bc0b79075b92f49d8adc86/a331c/image-8.png&quot;
            alt=&quot;image 8&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Then we can use it in the connect method to reject promise immediately:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/43bea156e0f49398ce9a69de9a0d4334/9c136/image-9.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 71.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAACC0lEQVR42p1U7W7TQBD0uyA1tu/D9t35zj7HCWnpD16NikeBgB8CBOIfUiXeBPCJZdYJJWlFkfpjtL7zZnd2Zp3spQ036yv79Zl0H3PhPq3OYE6iOTnfh/2wKt3tRWlfZbVZT97vqPUxhW4g2/bku0jGjuTMJRmzocZvyIQtGb+lUocHKJSfVRP5eZ/lOrxTbkPKDj+0HVLVjqlyiPxshoTEVKg25dItABvEB/guKk8rad9koDlpM5J3L5JjpnFH2o2k3EAV2GrXkzKBRO1JGqAJhMJnQJNZVAHR7bMLaSc+CBWSkC0pJBTaU4GOZdUC7hC1Q1GO/j8FwVDWPcm6S4J1MBFMBvy4I1lF4rsFuCuqjvUimMdFuMARdmYt7wrqOlJrrpJv2RzoGS9JwQDrR2qGzYJ6/ZzqYUs1jNM2QIoA3Tl2iH6u2oFydSyowLCUPglxGFkoHjUsowqMKDTuMXKpLFjzO8iBO5i1AIVmnij/y3CAKdfJ9Nfkwa7tdmTcGiu0gTGMkWyASbbHfb80K060PNMwl+3EXYQfk4TDBToJaFWDQaV7aBmpsJFy1hX65shl/R41RXNyFdKSyGJz0oLD+Q/uF/oHQ/teNT2/mIFfTwEK/TwWfJtBj0k1YKhD4vVZ1qXhZY7EnxPWadm9xwDJZiYFo/YZ9uo1utyC6We49OWJ4D+Jb/Dj5jdrp6QQWiAlSAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/43bea156e0f49398ce9a69de9a0d4334/507e6/image-9.webp 200w,
/static/43bea156e0f49398ce9a69de9a0d4334/28a80/image-9.webp 400w,
/static/43bea156e0f49398ce9a69de9a0d4334/8d2ea/image-9.webp 800w,
/static/43bea156e0f49398ce9a69de9a0d4334/68fc1/image-9.webp 1200w,
/static/43bea156e0f49398ce9a69de9a0d4334/e8d89/image-9.webp 1588w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/43bea156e0f49398ce9a69de9a0d4334/36ca5/image-9.png 200w,
/static/43bea156e0f49398ce9a69de9a0d4334/a3397/image-9.png 400w,
/static/43bea156e0f49398ce9a69de9a0d4334/a331c/image-9.png 800w,
/static/43bea156e0f49398ce9a69de9a0d4334/8537d/image-9.png 1200w,
/static/43bea156e0f49398ce9a69de9a0d4334/9c136/image-9.png 1588w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/43bea156e0f49398ce9a69de9a0d4334/a331c/image-9.png&quot;
            alt=&quot;image 9&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Of course, we could return some other type with additional information, like when to retry or apply dynamic rate limiting, but you get the idea. If you’d like to learn more about that pattern, please drop me a note, and I’ll consider that in further releases.&lt;/p&gt;
&lt;h3 id=&quot;centralising-queue-management-into-broker&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#centralising-queue-management-into-broker&quot; aria-label=&quot;centralising queue management into broker permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Centralising Queue Management into Broker&lt;/h3&gt;
&lt;p&gt;The implementation looks okay, but it’s not fully bulletproof. Also, queue management seems to be mixed with backpressure management. That’s not great, as it looks more like a general pattern. What if we centralised that into one place? Let’s do that!&lt;/p&gt;
&lt;p&gt;We’ll define the general QueueBroker, which is responsible for managing the queue and triggering processing, ensuring that only one processing is happening at one time. Let’s start by adjusting our API:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8ed0896cf1c6e1234c3751caf42cfc60/b5bed/image-10.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 67%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAABzklEQVR42o1TW27bMBD0aSyJpEiJS70s2XFsB0H713O06GWKoPVJDOQAyZWCUNBmaLmOExt1PwZLUeRwdmd3kqni533nnpOWdlPpHqMj7AHuKmJFO8QnxB8T0rPtt82SzbLxKm9YZQ2n+YxTO0Ka+iqErry2HQtTP0xiWf5OzYrzfPFiXNvLrO6puunzYt5nQCQJsH0k7GF9ES9ClwyVvyZJWmwLd8uGOg8CztwcL1WsgkrXMZU3bBCzYsF4DJeIY+U+AHte4k6k6GFP6CwI89lISB3SrjnBAfxjhSj3CKmVZ2QXCYtyw3mz9tSumboNG6jS1DLSHhUBozK6Thgndqtna9arr16BTHV3IFzwfPmFUcuLBP9WqIuthptTYX00/hwPAdjbf19CfLIOd+H0SQ1pNCVH4YMpCWpFqKPDqxqtZKHUQnUegDOhnRRKEvYywLi5DzESB0Ky6EPb+gxuGhwM/ZgFl0EcUgkmyaM51WgODDv59qF3/yr8U5R3TPXqlerbgZrVULebAYqHWJcDDg9I6wzxyXoq6PWYskQNDVJKTO1D/0lMSqhpUPGO6lP8uJ+ko0KsoRCznEh6xku7/WwK+4gi/9cMn82ycN/fAGfjgS+BSP6OAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/8ed0896cf1c6e1234c3751caf42cfc60/507e6/image-10.webp 200w,
/static/8ed0896cf1c6e1234c3751caf42cfc60/28a80/image-10.webp 400w,
/static/8ed0896cf1c6e1234c3751caf42cfc60/8d2ea/image-10.webp 800w,
/static/8ed0896cf1c6e1234c3751caf42cfc60/a48be/image-10.webp 1192w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/8ed0896cf1c6e1234c3751caf42cfc60/36ca5/image-10.png 200w,
/static/8ed0896cf1c6e1234c3751caf42cfc60/a3397/image-10.png 400w,
/static/8ed0896cf1c6e1234c3751caf42cfc60/a331c/image-10.png 800w,
/static/8ed0896cf1c6e1234c3751caf42cfc60/b5bed/image-10.png 1192w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/8ed0896cf1c6e1234c3751caf42cfc60/a331c/image-10.png&quot;
            alt=&quot;image 10&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;The queue is still built with an array of async tasks. It can manage the number of active tasks and the queue size.&lt;/p&gt;
&lt;p&gt;What’s Queue Task? Before explanation, let’s look now on the extracted QueueBroker code:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1161d8d62652b81c86aa09e9a0722794/9334e/image-11.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 159%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAgCAYAAAASYli2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAD3UlEQVR42p1W247jRBDNr/BAHNt9c3e7fU2cZGYB8U0IxNegXe0LCB5AEdL+1a6mpeJU28lkhmVnwsNRt+PU6bqcqvbq+xB+bO/d718V7t06t+/XhX2/Ee42SP8uk/6PTLqfV94Op244UN0MMbQjVfVIpe4oVw0Vr0QuQxSm5/2HFR7+6v231LT3n/R4jHa4izpsY+XamEsf4cFr8KlQgTbC/70qRDjV7kiq2sa83lERJiqrlqSuCaHwn15G6SJ7mpXuwyoT7qTqiWQ9ReU6EqpGyA2VpqXl1NsJrZ9ImzHqKhBDmo60GxJxVlg2eMRLhPjhJBGm9kM0vieFsE29JVl1VPkdabwTdqANDDapADUMfcJmWbOCCcPiYYmQ7UiNfxOb5kChRfjjPelmorrbkxkPZLd7qkYAq2lGkq5JUAyPfLsQle0fCQVCRHFiUdYkcCJ7UaAouXLII56R11xwuHPIORdrQXoHNZS6PRPaE/JHrf8m9vBoGna0A/p2IA9NDu2WTNhRCQ8ccqq4WMiv5pSAZE7BdVHYQ7zw7hhlfyAxHMm1ewrIJRuoxWjDXsv6yZ7BHmdPi+JOsoLKofbN8ueMEzyfTJlwt8mGq2yqkdsmbhBOjlAKrKxDYRbvbiW0dkKV76NGlUtUWXQIHTn7X4RfoyhVtUMvfxdVdwfCPeQAPZqkq9s7hT3U1cBhxhxEpd+mcHPxyj7+HKFzBzJ6jEr1lEOTuccIQ3co5NO4ER4P6ZDrFsxmojOeyoZ1yJ3i3YS225FCLgVCN+hxE5BXtGICe8/zEoewjfDz79Al2nZgb2cPWSpGb2Nf78kGtFY1G+WQEwv6UphFVgniap865Spkbh+h2sihc1FKVJlDFfjzunS3j680/6BDiBsTZQbvi7MnNxEihxL3Qc2tx2GCPAehwFAoJLeVfVYM93Q+Mgr7zEPVknVTNCiG7o4kMLr4sqqQaGV4XM0F4LnIEpPYF9yuPKXmzorcBJdeZnZXTbGqUd3+mDpFAiwXoVEYNnRDIpCqw53Tz0SXtYs8Ai+ErDF4Gs/h8djnLuGCrItqfl6wLu2/r4XrkM/DQUi4jdNznneQiuSbz2Ay8x6nC36GN2kY83XwpaJ4yMXbQzRm0R0MZ8JAKpHPRK8m5D+26BSLu0UsLZZCnIfnZ/FlQnx61O4uinYpSjOl8ZW0mDQa0pV63vO6uR5r14S4YP5kr2D8EYJ+OCPntfQPMHhA66X1DH6G8QUg/IhPGhQQnyIgO3HiQRAfP4DCvOr/wLN3bJsi0vyxVLofcMKvuCZ/wfp2hn/7uH8ZyVa43+D9T/8AbTDILa6NBNEAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/1161d8d62652b81c86aa09e9a0722794/507e6/image-11.webp 200w,
/static/1161d8d62652b81c86aa09e9a0722794/28a80/image-11.webp 400w,
/static/1161d8d62652b81c86aa09e9a0722794/8d2ea/image-11.webp 800w,
/static/1161d8d62652b81c86aa09e9a0722794/68fc1/image-11.webp 1200w,
/static/1161d8d62652b81c86aa09e9a0722794/43d96/image-11.webp 1600w,
/static/1161d8d62652b81c86aa09e9a0722794/50ac5/image-11.webp 1624w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/1161d8d62652b81c86aa09e9a0722794/36ca5/image-11.png 200w,
/static/1161d8d62652b81c86aa09e9a0722794/a3397/image-11.png 400w,
/static/1161d8d62652b81c86aa09e9a0722794/a331c/image-11.png 800w,
/static/1161d8d62652b81c86aa09e9a0722794/8537d/image-11.png 1200w,
/static/1161d8d62652b81c86aa09e9a0722794/1a152/image-11.png 1600w,
/static/1161d8d62652b81c86aa09e9a0722794/9334e/image-11.png 1624w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/1161d8d62652b81c86aa09e9a0722794/a331c/image-11.png&quot;
            alt=&quot;image 11&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Essentially, it’s refactored code we took from the connection pool. The difference is that instead of constantly running the background process, it is triggered once the item is processed. Then, it tries to drain the queue until the end.&lt;/p&gt;
&lt;p&gt;The QueueBroker handles concurrency by managing the number of tasks (connections) processed concurrently using the &lt;em&gt;maxActiveTasks&lt;/em&gt; parameter. It automatically tracks the number of active tasks and ensures that no more than the number of connections are active at any time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The queue task is an async, potentially multi-step process that can start and be completed at some point.&lt;/strong&gt; When we open a connection, we start such a task; we don’t know precisely when it will be released. We only know it will be done after we run our SQL query. As we know, various queries take different amounts of time. That’s why we’re giving queue user to ack (from “Accept”) the task processing end:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b0e8613d1a367bb3c7f2393741e64b96/9c136/image-12.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 47.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAABo0lEQVR42pWSW27bMBBFtZcClmhSFMWHKFGPqnbc9RTdUZEgX0EN7y6BCEwvaRVFgv7k42A4IjWPO1PMZ3P9oszTgennku8I+ymq2j6Vtb1WwvwshnEmaXxUpiepe+JNT0fZEasTLpP8f3g6Nu9hsouiDcneitZfaJjPb9M0RRPWaMMpSjMiwRzb7muUbo68HWIlPegiEsSSm3ccuHlLBVTcXItaj9TZS/TuRJ1fSI8PJMKJnF/JzifSy0rtDJZvpGEbF1JHJC06sndbWx8bOxIT7lYIhRaFi3CIc0sVLJOpVQvM3QqTqTJ2v8PbZGU+R658ur8VUo1kFFrVCwU3UOgCKh1ImYE0quncSNxM0BbVQEMODZkaKBUi5V3n1HbSNgcUGEJvv0czPZBbL9QvZ1rCSt5ACgSykISpPgdsgUhDwQBqfFN5IB8C1rhs9fzKVNgqM25Mj5uA5bLbIPIGwbfyqLfy73n3D4ndx7vXNBScfxeYaMoSj2k9dvK6pEo+rAhv/u/ntdFYmwZrgzJfmLC/kOWxqhPuMZ8/Qf4fcSpuf/wBJxEoHRI0/KMAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/b0e8613d1a367bb3c7f2393741e64b96/507e6/image-12.webp 200w,
/static/b0e8613d1a367bb3c7f2393741e64b96/28a80/image-12.webp 400w,
/static/b0e8613d1a367bb3c7f2393741e64b96/8d2ea/image-12.webp 800w,
/static/b0e8613d1a367bb3c7f2393741e64b96/68fc1/image-12.webp 1200w,
/static/b0e8613d1a367bb3c7f2393741e64b96/e8d89/image-12.webp 1588w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/b0e8613d1a367bb3c7f2393741e64b96/36ca5/image-12.png 200w,
/static/b0e8613d1a367bb3c7f2393741e64b96/a3397/image-12.png 400w,
/static/b0e8613d1a367bb3c7f2393741e64b96/a331c/image-12.png 800w,
/static/b0e8613d1a367bb3c7f2393741e64b96/8537d/image-12.png 1200w,
/static/b0e8613d1a367bb3c7f2393741e64b96/9c136/image-12.png 1588w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/b0e8613d1a367bb3c7f2393741e64b96/a331c/image-12.png&quot;
            alt=&quot;image 12&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;We’re returning the promise that it will end when the task is handled, so when ack is called.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks to that, we don’t need to sleep or wait; all processing is push-based.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The refactored connection pool implementation will look as follows:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/03b273545c1e6f0685d0e9f752d653e7/8df04/image-13.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 119.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAYAAAD6S912AAAACXBIWXMAAAsTAAALEwEAmpwYAAADaUlEQVR42p1U267TMBDsz5Amje34lji3Jj09hQNCfAlI8DuIB4TgBSSEKvFtSJUsLbNue25wAPEwcuzY49md9S42OryeZ/8l8+59VvgP2YphPyyB7AHwv6Xw18iFf78s/ees9G8Wne3243qksm6i0oFk1ZA0LQn+1h0pPZCoOhIGwFzaPn2vqnAD1URheozh+2Il229D/ZymbncI8zaG7ZPY9mOc12Os/Qa4jNbO0TZTgms3Udk+QmXMS3fGYSVrwrhf5MLtnZ1JV1Ms7EBFM1MOFThEVb0m6fqkSrkhrZ3VLUtHCPUIkEId1vz3BbMKHCzdGIVfk2VyvSbnW6pdIItQHMgs9jhc5gL+837d/p4wK+1e2ZHc+DTq3Qsyl0C3oz5cUT9d0TDie35KzfY5hc0zcu0FGRCy4ocVItkl8lPyzUntgBSwURMVmK+aKaHwI+VQBqf/ELKo9xJh8WLauDKAPR66jdsE93GXkHM4sbIokXCNPFWAxZoBKjcCMEs2OOj50K/k9wmlGcj7LUpkS76/INdvSSI8dljBHOlQl7aFEU2CtAEX1A8TsmNCtlEqKMRGBRSpPPhwjVIBEaPCt8Ua3C+ORAlckxzBNaGCQq3HyEYIOF4kc2AEwl1xOpBjA1WCL66guuL10x6MGoVukZalBGEBU/hpIT+Rb8UcPxCOOqOhAigl/iXU/MSoQHEXp394JZEvu1EYNpz46DFqwABK9yBBeCAo+YL0ZkMiXQp3HW4KGTlk4jumaDNGYycScFih5vi1aDQGjyZRcT6TSQM3gr+Vjd1L7iaqi4XvqeAOoxzmnh6hJrNTTWa36vGvZVNBoTVzNHrCk4LKMJFE2PxqBN6ugjrL5YPmwLXKb5sbRH4mvk3IppweeszZECDnIpZnc5rT2hHFCX9U6N0FcoU+14ykOyisMSKPTY26RAoMFHqAR24MBl1J41tcd5x7hOyo669ivXmCjvKY/IgLmh2tw47a4ZJqdJgao+4uSLQbkjCOzblReq/bcMgrP8TcrVEebEpIb5drLYfLXDIMnvPehGPr/5VwKezXyiDpev5hVDjUOhysbg/I1wH1doCjx/GMO3OfAFE/WC0Ivy2U7tBg4ZpsIt+aoI63/zPSS+k4BQhZ+pdZaT7l0r3Fre/+B+ls6T4i9Fc/Aamc05kU1sQwAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/03b273545c1e6f0685d0e9f752d653e7/507e6/image-13.webp 200w,
/static/03b273545c1e6f0685d0e9f752d653e7/28a80/image-13.webp 400w,
/static/03b273545c1e6f0685d0e9f752d653e7/8d2ea/image-13.webp 800w,
/static/03b273545c1e6f0685d0e9f752d653e7/68fc1/image-13.webp 1200w,
/static/03b273545c1e6f0685d0e9f752d653e7/4af63/image-13.webp 1416w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/03b273545c1e6f0685d0e9f752d653e7/36ca5/image-13.png 200w,
/static/03b273545c1e6f0685d0e9f752d653e7/a3397/image-13.png 400w,
/static/03b273545c1e6f0685d0e9f752d653e7/a331c/image-13.png 800w,
/static/03b273545c1e6f0685d0e9f752d653e7/8537d/image-13.png 1200w,
/static/03b273545c1e6f0685d0e9f752d653e7/8df04/image-13.png 1416w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/03b273545c1e6f0685d0e9f752d653e7/a331c/image-13.png&quot;
            alt=&quot;image 13&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;This is the critical line responsible for finishing the queue task:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;onRelease: ack, // ACK queue task on connection release&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The resolve method is passed directly into openConnection, which means that when a connection is released, it’s automatically resolved and the next task in the queue can be processed.&lt;/p&gt;
&lt;p&gt;Our connection pool no longer needs to track active connections manually. The QueueBroker handles this logic by limiting the number of concurrently running tasks.&lt;/p&gt;
&lt;p&gt;This setup supports multiple concurrent connections up to the specified limit (maxConnections). The QueueBroker ensures that the pool can handle as many connections as allowed and processes them efficiently.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After that now, we limited the resource usage and made the queuing code reusable in other places. And hey, we implemented one more pattern: Single-Writer.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;single-writer-pattern&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#single-writer-pattern&quot; aria-label=&quot;single writer pattern permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Single-Writer Pattern&lt;/h3&gt;
&lt;p&gt;What is the Single-Writer Pattern?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Single-Writer pattern is a concurrency design pattern in which only one thread (or, in JavaScript’s case, one function execution context) is responsible for writing or modifying a shared state.&lt;/strong&gt; This pattern is useful in scenarios where multiple threads or asynchronous functions might try to modify shared state concurrently, leading to potential race conditions or inconsistent state.&lt;/p&gt;
&lt;p&gt;In our connection pool, the &lt;strong&gt;QueueBroker&lt;/strong&gt; class is key. It manages a queue of tasks (like opening connections) and ensures that only a set number of tasks run simultaneously based on the pool’s capacity. This keeps the system from becoming overloaded.&lt;/p&gt;
&lt;p&gt;The single-writer pattern is implemented through the &lt;em&gt;processQueue&lt;/em&gt; method in the QueueBroker. Here’s how it works:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Single Point of Execution&lt;/strong&gt;: We use an &lt;em&gt;isProcessing&lt;/em&gt; flag to ensure only one instance of &lt;em&gt;processQueue&lt;/em&gt; runs at a time. This prevents multiple instances from processing tasks simultaneously, which could lead to inconsistencies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Controlled Task Management&lt;/strong&gt;: Tasks are added to the queue, and the QueueBroker decides when to process them, ensuring the system stays within its capacity limits.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the &lt;strong&gt;SimpleConnectionPool&lt;/strong&gt; class, task management is delegated to the QueueBroker. When a connection request is made, it’s queued and handled by the QueueBroker, ensuring that connections are opened only when the system is ready. This delegation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prevents Race Conditions&lt;/strong&gt;: By centralizing task management, we avoid multiple processes trying to modify the same data simultaneously.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simplifies Concurrency Management&lt;/strong&gt;: Only the QueueBroker modifies the queue or tracks active tasks, reducing complexity and minimizing bugs.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The result is a connection pool that operates efficiently and reliably, using resources effectively without being overwhelmed. The single-writer pattern makes the system easier to understand, maintain, and scale, setting a solid foundation for future growth.&lt;/p&gt;
&lt;h3 id=&quot;lifting-the-patterns-to-system-architecture&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#lifting-the-patterns-to-system-architecture&quot; aria-label=&quot;lifting the patterns to system architecture permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Lifting the Patterns to System Architecture&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The concepts of queuing, backpressure, and the single writer pattern are not limited to individual services or isolated pieces of code. These patterns are foundational principles in the design of large-scale distributed systems, where they play a crucial role in maintaining stability, consistency, and performance. Let’s explore how these patterns are applied in real-world systems, with specific examples of tools and technologies that use them.&lt;/p&gt;
&lt;h4 id=&quot;queuing-and-backpressure-in-microservices-amazon-sqs-and-aws-lambda&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#queuing-and-backpressure-in-microservices-amazon-sqs-and-aws-lambda&quot; aria-label=&quot;queuing and backpressure in microservices amazon sqs and aws lambda permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Queuing and Backpressure in Microservices: Amazon SQS and AWS Lambda&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In a microservices architecture, different services often communicate asynchronously using message queues. &lt;strong&gt;Amazon Simple Queue Service (SQS)&lt;/strong&gt; is a fully managed message queuing service that enables microservices to decouple and scale independently. However, managing the flow of messages between services is critical.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In a typical microservices environment, one service might produce messages much faster than another service can consume them.&lt;/strong&gt; For example, a data ingestion service might rapidly upload logs to a queue, but the log processing service might not be able to keep up due to its processing speed or external dependencies, like a database or external API.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SQS, combined with AWS Lambda, provides a potential solution.&lt;/strong&gt; SQS handles the queuing, while Lambda can process these messages. Importantly, Lambda automatically scales the number of function instances based on the number of messages in the queue. This scaling is effectively a form of backpressure: if the queue grows, more Lambda instances are invoked to process the backlog, and if the queue shrinks, the number of instances is reduced.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For instance, consider a media company that processes user-uploaded videos.&lt;/strong&gt; Videos are uploaded to S3, and an SQS queue triggers Lambda functions to process these videos (e.g., generating thumbnails and converting formats). If there’s a sudden spike in uploads, SQS will handle the queuing, and Lambda will scale up the number of processing functions. If the video processing service is slower due to the complexity of certain videos, backpressure ensures that the system can gracefully handle the backlog without overwhelming the processing service.&lt;/p&gt;
&lt;h4 id=&quot;queuing-and-backpressure-in-content-delivery-networks-cdns-cloudflare&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#queuing-and-backpressure-in-content-delivery-networks-cdns-cloudflare&quot; aria-label=&quot;queuing and backpressure in content delivery networks cdns cloudflare permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Queuing and Backpressure in Content Delivery Networks (CDNs): Cloudflare&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In content delivery networks (CDNs), managing the flow of requests and responses is critical for ensuring fast and reliable delivery of content to users around the world. &lt;strong&gt;Cloudflare&lt;/strong&gt;, one of the largest CDNs, uses sophisticated queuing and backpressure mechanisms to manage this flow at a massive scale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CDNs face the challenge of delivering content quickly, even during traffic spikes.&lt;/strong&gt; For example, when a new video goes viral, millions of users might try to access it simultaneously. If the CDN’s servers are not carefully managed, they could be overwhelmed, leading to slow load times or outages.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloudflare uses backpressure to manage how content is delivered to users.&lt;/strong&gt; When a server becomes overloaded with requests, it can slow down the rate at which new requests are accepted, queueing them as necessary. This ensures that the server remains responsive and all users receive their content, albeit with some requests being slightly delayed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For instance, millions of users might simultaneously request access to the video stream during a global sporting event’s live streaming.&lt;/strong&gt; Cloudflare’s CDN infrastructure will queue and manage these requests, applying backpressure to ensure the servers are not overwhelmed. This allows the CDN to continue delivering high-quality video streams to all users, even under extreme load.&lt;/p&gt;
&lt;h3 id=&quot;single-writer-pattern-in-kafka-consumer-groups&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#single-writer-pattern-in-kafka-consumer-groups&quot; aria-label=&quot;single writer pattern in kafka consumer groups permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Single Writer Pattern in Kafka Consumer Groups&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;In distributed systems, maintaining the order of events during processing is crucial for consistency, especially when dealing with real-time data streams.&lt;/strong&gt; Kafka’s consumer groups naturally implement a form of the Single Writer pattern to ensure that events are processed in the correct order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How It Works? Kafka divides data into partitions, each processed by a single consumer within a consumer group.&lt;/strong&gt; This approach ensures that only one consumer is responsible for processing the events from a particular partition at any time, maintaining the order of events and avoiding race conditions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consider an e-commerce platform processing order events like “OrderPlaced,” “OrderConfirmed,” and “OrderShipped.”&lt;/strong&gt; Each event is critical to maintaining the correct order state. Kafka’s consumer groups ensure that all events for a given order are handled by the same consumer, in the exact sequence they were produced, ensuring consistency in the order management system.&lt;/p&gt;
&lt;p&gt;If a consumer fails, Kafka reassigns the partition to another consumer, which resumes processing from the last committed offset, preserving the order of events. This mechanism aligns with the Single Writer pattern, ensuring that each partition’s events are handled by a single “writer” (consumer) at a time, thus maintaining the integrity and consistency of the system.&lt;/p&gt;
&lt;h3 id=&quot;conclusion-from-micro-scale-patterns-to-macro-scale-stability&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion-from-micro-scale-patterns-to-macro-scale-stability&quot; aria-label=&quot;conclusion from micro scale patterns to macro scale stability permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;strong&gt;Conclusion: From Micro-Scale Patterns to Macro-Scale Stability&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Queuing, backpressure, and the single-writer pattern are more than just programming techniques—they’re foundational principles that ensure the stability and scalability of large, distributed systems. Whether managing database connections in a Node.js application or architecting a global content delivery network, these patterns help you control the flow of data, maintain consistency, and keep your systems resilient under load.&lt;/p&gt;
&lt;p&gt;By understanding and applying these patterns, you can design systems that perform well under normal conditions and remain robust and responsive during peak loads and unexpected spikes in traffic. As your systems grow, these patterns will be critical in ensuring that your architecture scales gracefully and continues to deliver value to your users.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Also, last but not least. How do you like the new format? What would you change in it? What topics would you like to see here?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Mastering Database Connection Pooling]]></title><description><![CDATA[Welcome to the new week! Efficiently managing database connections is crucial for any application that handles multiple requests. Without…]]></description><link>https://event-driven.io/en/architecture-weekly-189-mastering/</link><guid isPermaLink="false">https://event-driven.io/en/architecture-weekly-189-mastering/</guid><content:encoded>&lt;p&gt;Welcome to the new week!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Efficiently managing database connections is crucial for any application that handles multiple requests.&lt;/strong&gt; Without proper connection management, your application can quickly become slow, unreliable, and costly to maintain. Database connection pooling offers a smarter way to handle database connections.&lt;/p&gt;
&lt;p&gt;However, connection pooling isn’t always ideal, especially in serverless architectures. Let’s explore the mechanics of connection pooling, understand its importance, discuss when it might not be suitable, and explore how modern serverless solutions redefine connection management.&lt;/p&gt;
&lt;h2 id=&quot;life-without-connection-pooling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#life-without-connection-pooling&quot; aria-label=&quot;life without connection pooling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Life without connection pooling&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Imagine you’re building a new e-commerce platform that helps small and medium-sized businesses create and manage their online stores.&lt;/strong&gt; It provides features like product listings, inventory management, order processing, and customer analytics. Initially, the focus is on delivering basic functionalities like product management and checkout processes.&lt;/p&gt;
&lt;p&gt;You start with a simple architecture. The backend is written in Node.js and PostgreSQL database. The naive version of your database access code could look as:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/39c4c948ecdaca29d1bd86d46e872b5a/b6207/2024-08-05-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 94%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAATCAYAAACQjC21AAAACXBIWXMAAAsTAAALEwEAmpwYAAACvElEQVR42pVUW67bNhT0Sqy3ZPEhkXrasnxv0vSnP11DgKwmKFAE7UfRVbTw6hKIwHQo+aZumuDefIxIUdLwzJkRdzKq3j021Z+xUr9GgfqQROrDOt4QvQBxyG9D+Ue0l+92TdJcf37okHa1yxKDNKmRpduYEH5M0xvWZ7e1/6ByeWb9+9ddGld/2WKCjJqPRWWXwpjlUNjFpP1SC7toZZZwL5fIIxBLeAMr4j0RiiXYi49xpBBH8u9dHutrI0aI1DidtZB5g0IZlHWDg7Ioie2eY2kgRQ8lBxTCIC9riLJDXR2dHyn9ukt4mdQFR3l2XX9C0x1hqgHjMMG+GtHOrN50KLRZScpDC602wuyO0K+vhCz/mmTceXx0B2lxPLZ4vIzobAvLKtkfxIECJYMGIAwF+A3nckVIBPvSJbHeCGMS5rmFml471V9QmxaXecA0DTAkVNKwN3w5Vhuir0G6deMnwpQV6suPzrz+CaKa0DYNzudhIz717JtFFH6L7AtCL1kqmmJPLktpRH6EL99LoZPPEH2NMBTXAxuaxtoxQsySXgn9Cx7Jk1TfIy/9OcIgKK+DmNDLk9N6hNETivqIojlD6mE1gE2nKQTnL6pQ5i16cXLiYHComa+iQp110FxfNzEnlB6Mi684TfQKP082o+5M4cVLqUXr3vwwY7qcMM8nTMzkq/lMg0bMZ2ZyOKIfR1TM4zB0q3GGYW9swywaxuaO0P+Ppehc086wdLlrL5Cqh7Vn5FlNyZRKg54kPhmV/BujL0xhDr3kOu9QVSM0f61wvwX484cvNcUTioJhVg9urh4o5QLFGB1Et1b33bGJeEIIxqbKu095Wjspe1d66IFHknHMovOSnsGnlTBSPG0yc5ViQBTpbRfKSm74/7n3Ldydh/z13rLhv/Okfs8dfrlH9HK8p9LfgkC8/QdHnhUpLz9fcgAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/39c4c948ecdaca29d1bd86d46e872b5a/507e6/2024-08-05-cover.webp 200w,
/static/39c4c948ecdaca29d1bd86d46e872b5a/28a80/2024-08-05-cover.webp 400w,
/static/39c4c948ecdaca29d1bd86d46e872b5a/8d2ea/2024-08-05-cover.webp 800w,
/static/39c4c948ecdaca29d1bd86d46e872b5a/68fc1/2024-08-05-cover.webp 1200w,
/static/39c4c948ecdaca29d1bd86d46e872b5a/7fc03/2024-08-05-cover.webp 1210w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/39c4c948ecdaca29d1bd86d46e872b5a/36ca5/2024-08-05-cover.png 200w,
/static/39c4c948ecdaca29d1bd86d46e872b5a/a3397/2024-08-05-cover.png 400w,
/static/39c4c948ecdaca29d1bd86d46e872b5a/a331c/2024-08-05-cover.png 800w,
/static/39c4c948ecdaca29d1bd86d46e872b5a/8537d/2024-08-05-cover.png 1200w,
/static/39c4c948ecdaca29d1bd86d46e872b5a/b6207/2024-08-05-cover.png 1210w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/39c4c948ecdaca29d1bd86d46e872b5a/a331c/2024-08-05-cover.png&quot;
            alt=&quot;2024 08 05 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Each HTTP request opens a new database connection.&lt;/strong&gt; This can work well for local development or small deployment, but setup quickly hits a wall as your user base grows. Users start complaining about slow load times, and your database server gets overwhelmed by too many open connections.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why does that become a problem?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Opening a new database connection for every request involves a time-consuming process&lt;/strong&gt; involving authentication, network setup, and session establishment. This adds significant latency, especially in high-traffic applications. Your database has a finite limit on how many concurrent connections it can handle. Without pooling, you risk overwhelming the database, leading to resource exhaustion and potential downtime.&lt;/p&gt;
&lt;h2 id=&quot;what-is-connection-pooling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#what-is-connection-pooling&quot; aria-label=&quot;what is connection pooling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;What is Connection Pooling?&lt;/h2&gt;
&lt;p&gt;Database connection pooling is a technique that allows applications to manage database connections more efficiently by reusing a set of established connections. Instead of creating a new connection for each database request, the application maintains a pool of open connections that can be reused, significantly reducing the time and resources needed to establish connections repeatedly.&lt;/p&gt;
&lt;h3 id=&quot;how-connection-pooling-works&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#how-connection-pooling-works&quot; aria-label=&quot;how connection pooling works permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;How Connection Pooling Works&lt;/h3&gt;
&lt;p&gt;Here’s a breakdown of how connection pooling operates in a technical environment:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Connection Initialization&lt;/strong&gt;: When the application starts, it initializes a fixed number of database connections and stores them in a pool.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Request Handling&lt;/strong&gt;: Whenever a request needs to interact with the database, the application borrows an existing connection from the pool instead of opening a new one.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Connection Reuse&lt;/strong&gt;: After executing the query, the connection is returned to the pool, making it available for future requests.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dynamic Pool Management&lt;/strong&gt;: The pool can dynamically resize, creating more connections if demand exceeds a pre-defined limit and reducing idle connections to save resources.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do you prefer visuals? There you have it!&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 499px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2d6db529ecb9ec561b981859ef946aa9/7c750/image-2.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB/UlEQVR42p1Ta4+bMBDM//9Z/dBGOTVtrs2FBkETwtM2NhgDgWCmm0dzd2kaVTWy1mjt8ezMeoIHQzENZ+HC+bbGxg3gLj0s5w4iP4MdxrtnJo8A902PdMeQhQwbAokDDpHm0NJgHP8D8DhG+nRVg+UaUml0Xf9w/0PAIwl7aIhRiGC7Bks87A2jhMVfCL4HHG9Wtsth+AqaORDhV+Q0dbLAUEev/Md/ZNgPoBIlTLYA30wRe1Pw7RPk7gmchTCthbX2T4a/xRWCIwgjJGkKxjK4/hZ125FmDWpTQhUKRSHR1Br77oC8bBDxCqXp7jMsqhaxIPesPTt4uagoCEwppFmOOMloXbxzOJMNRNG+AtbCQcl/QsRriGiFWvroivVZoU7BsCWM8MApX3IPbbFDX/p08QHDMJzAuaoxXIhMymSOYD0D284RulOk/ox0+3wG7BVkvEToPyPbfkG2mYOHL+iUQ9kBnOdI04RkYoiimP45JrbX5IBAazKYSpC1im6vL21DDA4kw6E6z4HWA0W7v5Z4LJe/Lfnqat9jF3MwoZBlDE3T4F5LHaOl0opqf9I81+2tKeNpm7UDKl2iqjRFTWLn8KmcspYoTE4HOZThkJWElySQuiENx5v+vdOH4yVdthKrYIHZ94/49PwBTy/nOP8xQ5C7ZMLh+preMvwFu/E59LAS9QcAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/2d6db529ecb9ec561b981859ef946aa9/507e6/image-2.webp 200w,
/static/2d6db529ecb9ec561b981859ef946aa9/28a80/image-2.webp 400w,
/static/2d6db529ecb9ec561b981859ef946aa9/dfbce/image-2.webp 499w&quot;
              sizes=&quot;(max-width: 499px) 100vw, 499px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/2d6db529ecb9ec561b981859ef946aa9/36ca5/image-2.png 200w,
/static/2d6db529ecb9ec561b981859ef946aa9/a3397/image-2.png 400w,
/static/2d6db529ecb9ec561b981859ef946aa9/7c750/image-2.png 499w&quot;
            sizes=&quot;(max-width: 499px) 100vw, 499px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/2d6db529ecb9ec561b981859ef946aa9/7c750/image-2.png&quot;
            alt=&quot;image 2&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Connection pooling can significantly enhance performance and reduce latency by eliminating the latency added by setting up a connection each time.&lt;/p&gt;
&lt;h3 id=&quot;implementing-connection-pooling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#implementing-connection-pooling&quot; aria-label=&quot;implementing connection pooling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Implementing Connection Pooling&lt;/h3&gt;
&lt;p&gt;Each mature development environment has a built-in connection pooling. For instance, for PostgreSQL:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Java has &lt;a href=&quot;https://github.com/brettwooldridge/HikariCP&quot;&gt;HikariCP&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;.NET has the &lt;a href=&quot;https://www.npgsql.org/doc/connection-string-parameters.html#pooling&quot;&gt;Npgsql connection pool&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Python has the &lt;a href=&quot;https://docs.sqlalchemy.org/en/14/core/pooling.html&quot;&gt;SQLAlchemy connection pool&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go has the &lt;a href=&quot;https://pkg.go.dev/github.com/jackc/pgx/v4/pgxpool&quot;&gt;pgxpool&lt;/a&gt; for PostgreSQL,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Node.js has &lt;a href=&quot;https://node-postgres.com/apis/pool&quot;&gt;pg.Pool&lt;/a&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll use the last one to address our connection management-related performance bottlenecks. The code could look as follows:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/167270b4ae7e6ee5c45e7db0d8c3d008/4c281/image-3.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 110.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAYAAADAQbwGAAAACXBIWXMAAAsTAAALEwEAmpwYAAADNklEQVR42pVV227cNhTc/yi8q8tKlHgTdZdWXtsJ0sf+QlH0r4Kkgl8C9A8TgAamQ23g2Elgpw+DQ0ricM5VuyKyf17X+lNq1fvoSn6IDvLDIbogivQvIY7Ne9pPh4P5a2fTZv3jPKHsa59lDiKvUWQ1BJFzf0yrV5Em1udZw7W93yWJ/mjzCTKuvwjtHoRyDyppHoyoHwz3SawJ9SLiWH0hGWjXXRbbtS1nSNt6EsJlHaztYNqeKh3iSIGkL4JEPiilvd/FV8VqmjOkm31T17il+33Xoa5bus+PIv3/CKPfSDjewpx/91U74Xrp8fbNjK5p4aoa2dG+SvqckApVd4a5fudldQMta3Rtg3FoMY0dZPlUpfoFwn25SjtD2cUbeYZiPMNLkbXMdstMB5XMOCHyNmT0B+KfEmq1eCmm7WAgEBtZQ7SP6yLvNvuqwtJMUPW1l26Bbm9RmnFT6vS77YLwcXZ0m9oLoXkphoFwhh3eetvf0b6B7hjL5ga24l7fQRbT5q4gWVBbFiNK0T8qpvWqnJDG+n6XxmbV1YxM9T6TdE2z/tQAoUcchWMcSXbst3oMyblYxvBrotiqAV8VkjBP7WqKHlmkfRFXyCODY0EIgyyzOOaaa42M2c6zimpbPmcICos8d4w9BeTOh5BsLqeRWgd5QlOMvnEjKtvDNQPanokaOmKEOS2ohomH2UVULhXdN46uN6jdKdhvhHsmpaH/I7Oc8tZUWYjIwaaMDzul3NyfIeSwJWN/VRIKh4C93PbPXGY81lBbddn5ZZ4wXI9omg5LO2NsRrbhjGE6wY0nVFULE/qdHWRNcNdBydDv+kdCZTp/vrnDcrrFPJ0xjgvm+Ux3Kh6yzKTZuiZAkSjY8K4Q1RPCUDYkDDOwlZMXcbh12GZhcOewV+D7iz38HJe2/L6wRYvFnP3JnuHqBWXJTHK+Hak8zLk0MZd18nx9TC4FTi+fEHLkl6JhfKbP2gw+t6Mvq8lvmUtrX+R9mMY+FC+Lm+t6Q9izi3wSS58m+nO4OEnUPzuO/VUxgyxwnz7efFF2GfH2FXz/C+CPJT7of9NIf+TPZn0GxvfFdbBEEs6Sg/j7Py2TbFVEueNIAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/167270b4ae7e6ee5c45e7db0d8c3d008/507e6/image-3.webp 200w,
/static/167270b4ae7e6ee5c45e7db0d8c3d008/28a80/image-3.webp 400w,
/static/167270b4ae7e6ee5c45e7db0d8c3d008/8d2ea/image-3.webp 800w,
/static/167270b4ae7e6ee5c45e7db0d8c3d008/68fc1/image-3.webp 1200w,
/static/167270b4ae7e6ee5c45e7db0d8c3d008/7408f/image-3.webp 1262w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/167270b4ae7e6ee5c45e7db0d8c3d008/36ca5/image-3.png 200w,
/static/167270b4ae7e6ee5c45e7db0d8c3d008/a3397/image-3.png 400w,
/static/167270b4ae7e6ee5c45e7db0d8c3d008/a331c/image-3.png 800w,
/static/167270b4ae7e6ee5c45e7db0d8c3d008/8537d/image-3.png 1200w,
/static/167270b4ae7e6ee5c45e7db0d8c3d008/4c281/image-3.png 1262w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/167270b4ae7e6ee5c45e7db0d8c3d008/a331c/image-3.png&quot;
            alt=&quot;image 3&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;It’s a simple code change, but instead of creating a new connection on each request, we’re asking the connection pool to “borrow” us a connection.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Imagine our e-commerce analytics dashboard or Black Friday’s demand. It could receive thousands of requests per second, each demanding data from a central database.&lt;/strong&gt; Without connection pooling, the overhead of opening and closing connections for each query would cripple performance. With pooling, the dashboard delivers real-time insights without delay.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Databases have a finite limit on the number of concurrent connections they can handle.&lt;/strong&gt; Without pooling, you risk overwhelming your database with excessive connections, leading to resource exhaustion and potential downtime.&lt;/p&gt;
&lt;p&gt;In applications where high concurrency is expected, such as e-commerce platforms, connection pooling helps manage the number of active connections efficiently. It ensures that the database isn’t overwhelmed by the number of clients. The connection pool offloads the database by maintaining connections shared across multiple clients, avoiding unnecessary load on your database infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As your application scales, so does the number of database requests it must handle.&lt;/strong&gt; Connection pooling enables applications to manage a high volume of concurrent connections effectively, providing a reliable way to handle traffic spikes without degrading performance or overloading the database.&lt;/p&gt;
&lt;p&gt;All that sounds great, but we cannot have only good things. So, let’s discuss…&lt;/p&gt;
&lt;h3 id=&quot;challenges-and-considerations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#challenges-and-considerations&quot; aria-label=&quot;challenges and considerations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Challenges and Considerations&lt;/h3&gt;
&lt;p&gt;While connection pooling offers numerous benefits, it comes with its own challenges that must be addressed to ensure optimal performance. As you saw in the code snippet, the connection pool gives you multiple configuration settings.&lt;/p&gt;
&lt;p&gt;Each implementation can have its own, but the most important are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;pool size&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;connection timeout.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Choosing the right pool size is critical to avoid bottlenecks or resource exhaustion.&lt;/strong&gt; Too few connections can lead to queued requests, while too many can overwhelm the database resources, degrading performance.&lt;/p&gt;
&lt;p&gt;It’s not possible to say what size is best for your application. That’s something that should include the specifics of your:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;workload characteristics,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;database capacity,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;application requirements.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;We should analyze our application’s workload to determine the optimal number of connections, considering peak and off-peak usage patterns.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Having that, take a look at database capacity and understand its limits. That’s critical for preventing overloading it with too many connections.&lt;/p&gt;
&lt;p&gt;What does “too many” mean? It can mean something else for each application. The basic information comes from considering the concurrent users and requests our application needs to support. But of course, we should understand our business use case to set up the metrics based on the real demand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Striking the right balance between performance and resource usage requires carefully tuning pool settings.&lt;/strong&gt; While a larger pool can handle more requests, it consumes more resources. Conversely, a smaller pool can lead to contention and delays.&lt;/p&gt;
&lt;p&gt;We can try to implement dynamic pool sizing algorithms that adjust the pool size based on current demand and resource availability. Of course, that’s tricky, and we should start by continuously monitoring and fine-tuning the settings as our system evolves.&lt;/p&gt;
&lt;h4 id=&quot;connection-leaks&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#connection-leaks&quot; aria-label=&quot;connection leaks permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Connection Leaks&lt;/h4&gt;
&lt;p&gt;Connection leaks are one of the most common issues. They occur when connections are not properly returned to the pool, leading to a gradual exhaustion of available connections and eventual application failures. This often happens when a developer forgets to close a connection after use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using try/finally block can help with that.&lt;/strong&gt; We did that in our snippet above. That ensures connections are always returned to the pool, even if an exception occurs.&lt;/p&gt;
&lt;p&gt;Of course, we should cut the number of things developers need to remember. That’s why it’s worth including such cleanup in some common wrappers, reducing the likelihood of misuse. Our updated helper can look as follows:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/8664588a69d70a77f6fae9cc5b06a877/4c281/image-4.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 140.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD2klEQVR42p1V23LjRBD1f0AcybrMjOYmaSRb8jUJ8MJvcPkqUKXY4g0KPm+3Jq7mzMiBbNhNdvfhVEuydab79OnWokr0Twen/8qMfJNcyd+T5D+sPh1v0mv5d5JUPy9M1k7f7wYSrvFlWRNjDfGiJV42xHCfZ/ZVZCvjS7yTZ+Z+sVqpX2w5UJW2b5mqH5isH+SqfVBl86Bwv0oVIF9Emsq3ICPEaVGkZuqqkaR1nuuamrInYzsyXU+sqClNJIH0RYDIh0wR7xfJFZ+0O5CoB++ahm6OA/V9R03TUlnYLyD8ik9qfSJ9+M6bbqTdtqe724G61lFtG5CilOQzM5TdgfT+G1/ZAynZkmtb2qy7iErUn0m4FFOloaHeeS2OJMUYO1cWDhp2FLpX5CHT0PUudBQk8hVCO5JSO1+xgUq8HAhY6SLZTDxf83I+4NUMhR5INnsv6z2p7kRCb0jykWr1bTwg/Pkxy5lQv6ThXLLp77zpb8hs7iKpBkx9olrfQsdtzJiBLECwNXHWzxnnMXMvxUBZqu4XWaonhZLzqvW5gF4Sf6p6KmVPWQ4t8z6+FOyTXMvXm1JkZjI4sUy0Z6kJkQo2A1NDVYUMWI2SLamqozw25QXCbKknV49kTe8H2GbjjsQ15tk00DV4cSSt1sRyTBH+V4J4tdLo9lMd39NQTnY7krvb+R5+HG5OpGyPA0Zq7Y6MGoANiayhxoZnI3WYrMZuqcj0/wnDQLdySxu79y0mxa135ORIjYAMypKwIduaSoEOo/tC9TG7pyP5fpcTMUl0kOfWp4WipJBUQMcSemaliih40BQ6CviRt+jmS7a5EPbV6BW6WecYN+7IQi8tNxEM/kuuK0oveN3YzNHWHPxo9mRBIEAYGsGRjQAKbJ348uoTZjldShB2aMLglUY3G2xvZBe2ddDpUavsGdlHuxx8yKHN1ZL762VFydeCMD0AylvCzFezoUPJyTKULCOul2KWIR74lBCT4rBhdO68gP8qhzJNCw92JLBk471qo3V0qABbnUtYqN6SMZvHJfy0KXJy+ASMcu9Vu8ZeBPmwJrtF6TB2IA1RQRZWYjHkKBX+mxdF/bjOnmgIwnDTua0/no403N2S65ENPGjDtAhEeFFwQ7KyeF7H50oaLI2AZxmG5RBGSUrnd+ORxvWB1v2O+m4bo+A2knFm4nWIgeTxmrPogAuhCraRv3IWFoB7h/TPeW7P+PEcrgOw/s9oCqKMMbnEcD9DnUH47pLhtMDCnGS1Ce734dsatsm/ePZBL57FD37o81T+sErUH7DCPU767UsQFisy/RMZ/vgP1aQacN1+va4AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/8664588a69d70a77f6fae9cc5b06a877/507e6/image-4.webp 200w,
/static/8664588a69d70a77f6fae9cc5b06a877/28a80/image-4.webp 400w,
/static/8664588a69d70a77f6fae9cc5b06a877/8d2ea/image-4.webp 800w,
/static/8664588a69d70a77f6fae9cc5b06a877/68fc1/image-4.webp 1200w,
/static/8664588a69d70a77f6fae9cc5b06a877/7408f/image-4.webp 1262w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/8664588a69d70a77f6fae9cc5b06a877/36ca5/image-4.png 200w,
/static/8664588a69d70a77f6fae9cc5b06a877/a3397/image-4.png 400w,
/static/8664588a69d70a77f6fae9cc5b06a877/a331c/image-4.png 800w,
/static/8664588a69d70a77f6fae9cc5b06a877/8537d/image-4.png 1200w,
/static/8664588a69d70a77f6fae9cc5b06a877/4c281/image-4.png 1262w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/8664588a69d70a77f6fae9cc5b06a877/a331c/image-4.png&quot;
            alt=&quot;image 4&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;But even with that, we should continuously monitor our connection usage to identify if we don’t have potential connection leaks.&lt;/p&gt;
&lt;h4 id=&quot;security-considerations&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#security-considerations&quot; aria-label=&quot;security considerations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Security Considerations&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;As we share connections between multiple operations, we also share the credentials we connect with.&lt;/strong&gt; This can make it harder to use some features like &lt;a href=&quot;https://www.postgresqltutorial.com/postgresql-administration/postgresql-row-level-security/&quot;&gt;Row-based security&lt;/a&gt;. We’ll always connect to the database as the same user.&lt;/p&gt;
&lt;p&gt;This can push you to implement Access Control in the application layer. The other option is to have multiple connection pools. Each connection pool will be related to specific Permission Groups/Roles (e.g. different connection pools for admins and regular users).&lt;/p&gt;
&lt;p&gt;Maintaining a large pool of open connections can pose a risk in environments where security is a critical concern. If a connection in the pool is compromised, it might lead to unauthorized access to the database.&lt;/p&gt;
&lt;p&gt;Ensure proper security measures, such as encryption and access control, are in place when using connection pooling. If security cannot be guaranteed, you may consider alternatives that offer tighter control over connection access.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;connection-pools-microservices-and-conways-law&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#connection-pools-microservices-and-conways-law&quot; aria-label=&quot;connection pools microservices and conways law permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Connection Pools, Microservices and Conway’s Law&lt;/h3&gt;
&lt;p&gt;Nowadays, &lt;a href=&quot;https://www.architecture-weekly.com/p/papers-we-love-2-how-do-committees&quot;&gt;Conway’s Law&lt;/a&gt; always has to be involved, aye? But why here?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using a connection pool library like &lt;a href=&quot;https://node-postgres.com/apis/pool&quot;&gt;pg.Pool&lt;/a&gt; in Node.js is a good first step toward implementing connection pooling for many applications.&lt;/strong&gt; It’s simple, easy to set up, and provides a basic level of pooling out of the box. You already saw that refactoring from a no-pooling setup should be pretty easy to medium-sized applications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Yet, it assumes that you have a pool per deployment.&lt;/strong&gt; That works great for monolithic or not highly distributed systems, but…&lt;/p&gt;
&lt;p&gt;What if you have multiple instances of the same service and doing &lt;a href=&quot;/en/setting_up_nginx_with_aspnet/&quot;&gt;load balancing&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;What if you have microservices with dozens of deployed services?&lt;/p&gt;
&lt;p&gt;What if you not only have Node.js applications but also write in other stacks (e.g. Java, C#, Python, etc.)?&lt;/p&gt;
&lt;p&gt;This can result in repeating the connection pool setup in each dev environment, which can be painful to maintain.&lt;/p&gt;
&lt;p&gt;Also, if all those applications use the same database (e.g., shared per database schema), they won’t use the same pool, which can again lead to pool exhaustion.&lt;/p&gt;
&lt;p&gt;For these situations, you might consider using a dedicated service for pooling. It’ll act as a proxy between our application and the database. The examples can be as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;MySQL: &lt;strong&gt;&lt;a href=&quot;https://proxysql.com/&quot;&gt;ProxySQL&lt;/a&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;a href=&quot;https://dev.mysql.com/doc/mysql-router/en/&quot;&gt;MySQL Router&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MSSQL: &lt;strong&gt;&lt;a href=&quot;https://github.com/mkleehammer/pyodbc&quot;&gt;odbc-bcp&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Oracle: &lt;strong&gt;odpi-pool, &lt;a href=&quot;https://github.com/mariadb-corporation/MaxScale&quot;&gt;MaxScale&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PostgreSQL: &lt;strong&gt;&lt;a href=&quot;https://www.pgbouncer.org/&quot;&gt;pgBouncer&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We’ll use the last one as an example.&lt;/p&gt;
&lt;h4 id=&quot;connection-pool-proxy-service&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#connection-pool-proxy-service&quot; aria-label=&quot;connection pool proxy service permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Connection Pool Proxy Service&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;pgBouncer&lt;/strong&gt; is a specialized connection pooler for PostgreSQL, offering advanced features beyond those available in basic libraries. It’s known for its high performance and low memory footprint, making it suitable for demanding applications. pgBouncer acts as a middle layer between your application and the PostgreSQL server. It maintains a pool of connections to the server and multiplexes client connections, managing resources efficiently.&lt;/p&gt;
&lt;p&gt;To configure it, we need to install it on our server:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;apt-get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; pgbouncer&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Setup the configuration and reference to our PostgreSQL database in &lt;em&gt;pgbouncer.ini&lt;/em&gt;:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;ini&quot;&gt;&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;databases&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token key attr-name&quot;&gt;mydb&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;host=localhost port=5432 dbname=mydb&lt;/span&gt;

&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;pgbouncer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token key attr-name&quot;&gt;listen_addr&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;127.0.0.1&lt;/span&gt;

&lt;span class=&quot;token key attr-name&quot;&gt;listen_port&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;6432&lt;/span&gt;

&lt;span class=&quot;token key attr-name&quot;&gt;auth_type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;md5&lt;/span&gt;

&lt;span class=&quot;token key attr-name&quot;&gt;auth_file&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;/etc/pgbouncer/userlist.txt&lt;/span&gt;

&lt;span class=&quot;token key attr-name&quot;&gt;pool_mode&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;transaction&lt;/span&gt;

&lt;span class=&quot;token key attr-name&quot;&gt;max_client_conn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;100&lt;/span&gt;

&lt;span class=&quot;token key attr-name&quot;&gt;default_pool_size&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;20&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Having that, we can launch the service&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;bash&quot;&gt;&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;service&lt;/span&gt; pgbouncer start&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Now, we need to modify our application. We cannot use the application level pool anymore.&lt;/strong&gt; If we did, then they would clash with each other. If we’re using the connection pool proxy, we must create a client each time and let the proxy manage connections internally. The updated code would look like this:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/638fc098e27564f9c15b1aee40e15bd0/33c03/image-5.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 126%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAACXBIWXMAAAsTAAALEwEAmpwYAAADiUlEQVR42p1VyY7cNhTsH/Fo37iJojaq97aDHHN38lWZ9ikL5pjlB22AA7wUpfasGKTtwwMpSizVe1V8XLFY/nxU+t/CyD/CiN8lobhLInEXhfzqiEP+ZxTwf6JI/LKSqTn/ZHsqrXJJWlOaKErmwDxdIpvXl8iSx/nXwPcuzwylsfq0ihL+a11sSKXd57LWThe909y4WjUufMddeMNccFO5MGAuirgL/BgyF0fiEtwlMf/siWB+u0pjeTblSCzRjuUNidRQWWgqGNhUigpeE2cdlaUhlIJY2VIOxvgBIV2ACA/kPNM4FJ9WcSDPvd5QM46uESN19URq7ElbS/12S+vjibbTB+rwzdDt6bD9kTb2PWl8V2YNRa8AwVDVltabkxvsnobNkdp+R4avwbSlomgozzVBrGWegT0yqS5MXzEMQ34WRU+D3Dmue2qmnjrTk5WWmr6dnxnv5vSeRhjwN1IGoFeSjXtXioF609DpBMYAUtKASY2N4rLxrXgBmKWauD06tf+BRLehvjV0OIxkYacOcyWbB2ZXA5bKOmU/EK+3JLihobc0jRPtNlvq+wl18zUz1wNW9eQYh8LsgM0dDKthaAMRunnMUgiC+F/AMKjOnA/E1OjiWMwsLiadY0mTX5+yV5khnTLVLggZBUFFod8c8AfzPjfxFYAVzuHAJ1eXPdU51EXKtbeNXpOUI0y8htrmeobewCNbu708kIUonjHn3jYjlbkhVkGQrL6SIWpYYFNbjk4AyJ8a3Wyoaibi8GWEdL8p5TRVZ4kzHIZzN1mKj/rFL0WIl3Fe80YPH5/93kdAnOWqmDuJ8+pmuUIP9P1QLpFKSjOMkZzV973Rr83reO/PdpYq59cfajiJHe353ul6ILmHMLuezHFN7Qmi4Ajqg0UHGsi2B2pRjlKhOWhDAiWx44mkGJz3MrAWwAKGHeqtG9BlpEWzXW+Ja1hJwMyFQtToi2ASy9mTCdJPLqXw9X11Uny7z/PavT/tabff0Wa3Q8fpyA5oFl0/n+XOGDDRZBoDG4HAgE6EuVnCcWYA/rXbABA+c127o9ZsAXSkWlnMN6T1NLNJ4qVmyUWcmSXisjanPDOEsrdV2fmm+QV3xz1sdH/zzo/sPrhZAgD3UBPxfHyy/mVOOZK3qzw3Z8FGz2BWKntyy2Uvbry34tmtF8fsYxSIv/G33/z9+n0hfgfLv3CdfPwPVVXBifs8n50AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/638fc098e27564f9c15b1aee40e15bd0/507e6/image-5.webp 200w,
/static/638fc098e27564f9c15b1aee40e15bd0/28a80/image-5.webp 400w,
/static/638fc098e27564f9c15b1aee40e15bd0/8d2ea/image-5.webp 800w,
/static/638fc098e27564f9c15b1aee40e15bd0/5ad0e/image-5.webp 1140w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/638fc098e27564f9c15b1aee40e15bd0/36ca5/image-5.png 200w,
/static/638fc098e27564f9c15b1aee40e15bd0/a3397/image-5.png 400w,
/static/638fc098e27564f9c15b1aee40e15bd0/a331c/image-5.png 800w,
/static/638fc098e27564f9c15b1aee40e15bd0/33c03/image-5.png 1140w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/638fc098e27564f9c15b1aee40e15bd0/a331c/image-5.png&quot;
            alt=&quot;image 5&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Same for other services. Each can just use the native connectivity without being aware of the pooling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A proxy service like pgBouncer can help speed up and handle more transactions without putting pressure on the application.&lt;/strong&gt; It can also scale independently from the application. As it’s designed to handle connection pools purely, it can be better optimised and more suitable for resource-constrained environments.&lt;/p&gt;
&lt;p&gt;As always, the pros can easily become cons depending on the perspective. They require additional configuration and maintenance compared to library-based solutions. While efficient, they lack some advanced features like query rewriting or protocol-level optimizations found in other solutions.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-proxy-connection-pool-might-not-be-enough&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#when-proxy-connection-pool-might-not-be-enough&quot; aria-label=&quot;when proxy connection pool might not be enough permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;When Proxy Connection Pool Might Not Be Enough&lt;/h3&gt;
&lt;p&gt;Even with pgBouncer-like solutions, there might be cases where your application demands more dynamic and automated solutions, especially in serverless environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;While working on &lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt; and &lt;a href=&quot;https://github.com/event-driven-io/emmett&quot;&gt;Emmett&lt;/a&gt;. I implemented connection pooling by default, similar to the one I showed above.&lt;/strong&gt; But I forgot to enable a regular non-pooled approach. I was quickly reminded that &lt;a href=&quot;https://github.com/event-driven-io/Pongo/issues/35&quot;&gt;I was wrong&lt;/a&gt;. The fix was simple, but let me explain why I enabled such an option.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Connection pooling can become problematic in serverless environments.&lt;/strong&gt; Serverless architectures, like those built on AWS Lambda or Azure Functions, are designed to be stateless, with each function execution being short-lived and independent. Maintaining a pool of connections across multiple executions isn’t feasible, as each invocation might spin up its connection pool.&lt;/p&gt;
&lt;p&gt;This can lead to excessive resource consumption and overwhelm the database with too many simultaneous connections. Since serverless functions scale automatically based on demand, they can quickly exceed the database’s connection limits.&lt;/p&gt;
&lt;p&gt;Instead of traditional connection pooling, serverless applications often require alternative solutions like database proxying or managed connection pools, allowing efficient connection management without persistent states.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For applications with very low traffic or infrequent database access, the overhead of managing a connection pool may outweigh its benefits.&lt;/strong&gt; The time and resources spent on maintaining the pool might not be justified if the application doesn’t require rapid database access or high concurrency.&lt;/p&gt;
&lt;p&gt;In such cases, a simpler approach of opening a new connection for each request might be more straightforward and efficient.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ok, but what if our solution has higher demands and traffic, and we’re into serverless?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Of course, you can maintain your instance of VM with pgBouncer, but that’s not serverless, as it requires stateful service. We could also implement a single-writer pattern by queuing all operations to a single queue, but then it’s not the most scalable solution (to say mildly).&lt;/p&gt;
&lt;p&gt;Typically, we need to use the managed service. An example of that can be the &lt;a href=&quot;https://aws.amazon.com/rds/proxy/&quot;&gt;AWS RDS Proxy&lt;/a&gt; (for Azure, you could use &lt;a href=&quot;https://learn.microsoft.com/en-us/azure/postgresql/flexible-server/concepts-pgbouncer&quot;&gt;Azure Database for MySQL/PostgreSQL Flexible Server Proxy&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AWS RDS Proxy is a fully managed database proxy.&lt;/strong&gt; It’s designed to enhance the scalability and reliability of applications that use Amazon RDS databases. It is particularly useful in serverless architectures where maintaining persistent connections is challenging.&lt;/p&gt;
&lt;p&gt;RDS Proxy is a man in the middle between your application and the database, managing connections efficiently. It allows serverless functions to share a pool of database connections, reducing the overhead of establishing new connections with each function invocation.&lt;/p&gt;
&lt;p&gt;To set it up, you must have RDS Proxy ready and ensure that your AWS Lambda has access to it (so if IAM Role has proper setup). After that, we just need to adjust the setup to be:&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2346dbae2ab73e9a8ef3778ac1a04075/33c03/image-6.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 140.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAcCAYAAABh2p9gAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD3ElEQVR42pVW247bNhT0l9i630hK1I2SfNvd9PLctwLtVzUOEBRosQ/tS/uDCcAFJkNZm904WcT7MCAp26Nz5pwz9KoI1G9vSv1/2qm/vFDch568D31573viagSe+NvfiP98X/6+UlFz+mXqkU+VDSONKCwXVIgjB72A+7D6cr+A37dp3CAOynerMBR/1NkOVdx9yCptdWqslo2tytr6a2H5ZuttihmBz7NXEIJ7uUDYMBAfXBDcv11FgTo12YAi1LZIasioQZZqpAXfnpdIRQVRdMiyBpQCRdYiYYR8AUhMEumIrMso8OS7VbBRJ6N3qI2xtRjQlRNK06MaR3T7Pba3d9hPP6LXe5juiJv9z9gNd6iqEVlcw/+KkBGW/HB3eGPNeMSwu0E/HNCqLYq8ZWQ1kqQCi8XIGySxRsZM8iXSryL0PHGSaY8uGa1QPVQ3QuQGbUpCd+5HSEbubcTnNB3c+dspk9BVLqtGm4sRujJo6gFtw/TbAY0eMZqJzxhtWpFELiTPcUE4t0hWs21Kiq5YhIopcc+i5DyfV9cy5TfIXohQmKMV3QF1a3DYG+y2BtPYM9IWEYnOKcorCSm06I+2vP0JM6mucTyQ+DAQBoZVP/fZlYTuIMaDleYGTcMK1zuUaYtBUdOxRTMOc3VdUa5MmfoUpRVli1RWSMsamdJIiHjWs0WpBoSBul5D9pSNKXymOCmyRkqyeRWahA1bxzDtKwhdH6ZJw+kwNmETi+TAIa/hrdlzrhBL/72c7iXhJj8VnNU40dZzjRrIM/zX4CLCnNaj0taGS0pOK7efz4E7v5KwSFhRsbUtXafPJmiOIq0Mom1RVC3XDnnePBu172nIoZ/Ezu7lAZpjl7piSAMnxfyD78pwmTLbQsatzZwbzx5IPxR08bx7vYZJVJ4EW2Kzya3ns6LOTdYOzllYXT7z/cVZHp3GExfpPyN0jq2pXRpWVoRs6JA9KYmcze6XdHC6TKTnqF2fFow6p0QZJ+eFlPPTTh1xkEdb1xw1M6K6HdHcbaHaHg3Hr+oNlDGQNFwnQ86+jfliZxaLP36pYcIIajXZaXuE3u45vwdI2SB390qikObVbF8pXSeOJOLYuTe1Tj6Dtx5n/Tx66hQGvDYjZW9+2GJ3N2FL9HSYiTY20BhKVr1jC0lKUeuGJlzD9O2yb5w7WVE0zuJIyKvPzbIU/UcawUOUlw80hQfqxX01r/ziw2ZdzOsjNuundbOWH11Qga/erjjHp1JN7mK3ccBL28Ff1hnlcuE/4elPwOP5fNFHy0X/K2++fynse99XfzqEy/oKvPc89Y/j+gQVBRZuitKUPQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/2346dbae2ab73e9a8ef3778ac1a04075/507e6/image-6.webp 200w,
/static/2346dbae2ab73e9a8ef3778ac1a04075/28a80/image-6.webp 400w,
/static/2346dbae2ab73e9a8ef3778ac1a04075/8d2ea/image-6.webp 800w,
/static/2346dbae2ab73e9a8ef3778ac1a04075/5ad0e/image-6.webp 1140w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/2346dbae2ab73e9a8ef3778ac1a04075/36ca5/image-6.png 200w,
/static/2346dbae2ab73e9a8ef3778ac1a04075/a3397/image-6.png 400w,
/static/2346dbae2ab73e9a8ef3778ac1a04075/a331c/image-6.png 800w,
/static/2346dbae2ab73e9a8ef3778ac1a04075/33c03/image-6.png 1140w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/2346dbae2ab73e9a8ef3778ac1a04075/a331c/image-6.png&quot;
            alt=&quot;image 6&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Of course, we still need to monitor it to ensure that our RDS Proxy settings match our needs.&lt;/p&gt;
&lt;p&gt;AWS RDS Proxy is a decent service, but it requires configuration and understanding of AWS services, which might introduce complexity. Plus, it adds additional costs to the managed service. No free lunch!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implementing Connection Pooling Proxy is a trend for Serverless Offerings&lt;/strong&gt;. &lt;a href=&quot;https://developers.cloudflare.com/hyperdrive/configuration/how-hyperdrive-works/&quot;&gt;Cloudflare Hyperdrive&lt;/a&gt; and &lt;a href=&quot;https://supabase.com/blog/supavisor-postgres-connection-pooler&quot;&gt;Supabase Supavisor&lt;/a&gt; are examples of such services. They’re designed specifically for serverless environments, addressing the unique challenges of stateless architectures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cloudflare Hyperdrive is a global connection pooling solution that optimizes database interactions across distributed environments.&lt;/strong&gt; It manages a global pool of connections and routes queries to the nearest pool to reduce latency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Supavisor is a connection pooler for PostgreSQL developed by Supabase.&lt;/strong&gt; It is designed to handle high concurrency and rapid I/O operations. Built with Elixir, it efficiently manages connections and offers features like query load balancing.&lt;/p&gt;
&lt;p&gt;They both shine in high-concurrency, real-time applications. However, they are both limited to supported databases (for Supabase, it’s just PostgreSQL) and may not support all native connection settings.&lt;/p&gt;
&lt;p&gt;Plus, of course, they’re only limited to their products. Still, if you’re into serverless, check if your provider has such a proxy service. If you need to be on a high scale, you may require it!&lt;/p&gt;
&lt;h2 id=&quot;best-practices-for-implementing-connection-pooling&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#best-practices-for-implementing-connection-pooling&quot; aria-label=&quot;best practices for implementing connection pooling permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Best Practices for Implementing Connection Pooling&lt;/h2&gt;
&lt;p&gt;Let’s recap the learnings and try to give a recap of what Implementing connection pooling effectively requires adherence to best practices that ensure optimal performance and resource management. Here are some recommendations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You can start simple by using reliable connection pooling libraries.&lt;/strong&gt; Choose a connection pooling library or framework that suits your application’s needs. These libraries provide built-in support for connection pooling and offer various features to customize and optimize pool behaviour.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Handle connection release gracefully to prevent connection leaks.&lt;/strong&gt; Ensure you release connection (e.g. in a try/finally block or reusable helper). You can also implement retry logic to handle transient connection failures gracefully. Provide fallback mechanisms to ensure continuity of service in case of connection issues.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If you’re into microservices, Kubernetes, or multiple tech stacks, consider using a proxy service like pgBouncer to unify load balancing.&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If you’re into Serverless, check managed services available in your platform like AWS RDS Proxy, Cloudflare Hyperdrive, Supabase Supervisor.&lt;/strong&gt; Traditional connection pooling may not be the best fit in serverless environments due to the stateless nature of serverless functions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Monitor and tune pool settings regularly.&lt;/strong&gt; This is essential to maintaining optimal performance. Track metrics such as connection usage, wait times, and pool size to identify potential issues. Continuously evaluate your system as it evolves.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Database connection pooling is essential for optimizing performance and resource utilization in modern applications. By reusing existing connections, pooling reduces latency, improves scalability, and ensures efficient resource usage. However, it also requires careful configuration and monitoring to avoid pitfalls like connection leaks and resource exhaustion.&lt;/p&gt;
&lt;p&gt;By implementing best practices and leveraging reliable connection pooling strategies, you can build performant and resilient applications that handle high traffic and demanding workloads.&lt;/p&gt;
&lt;p&gt;In software development, mastering connection pooling is like having a well-oiled engine—it keeps everything running smoothly and efficiently. So, take the time to understand and implement connection pooling in your projects. Your applications (and your users) will thank you for it.&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, and putting pressure on your local government or companies. You can also support Ukraine by donating, e.g. to the &lt;a href=&quot;https://savelife.in.ua/en/donate/&quot;&gt;Ukraine humanitarian&lt;/a&gt; organisation, &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;Ambulances for Ukraine&lt;/a&gt; or &lt;a href=&quot;https://redcross.org.ua/en/&quot;&gt;Red Cross&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Event Sourcing on PostgreSQL in Node.js just became possible with Emmett]]></title><description><![CDATA[Last week, I announced Pongo - Mongo, but it was on PostgreSQL. So, the Node.js library allows using PostgreSQL as a document database…]]></description><link>https://event-driven.io/en/emmett_postgresql_event_store/</link><guid isPermaLink="false">https://event-driven.io/en/emmett_postgresql_event_store/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/21de7aa5676ecf81e6a64ae81d26984d/a331c/2024-07-12-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4eAAAuHgGqqWO7AAADoElEQVR42h2T6U/bBRiA+2E6NhQ6jpYOKMVxjBWh0NIWVipj/RVKOVpaWijlmi3X2GBdRmkrgzIOkSGHZhyBEaMzW9BMjYkSjYkxWUxm4kz0g3ExmnhEl8z4Fzz+5E3er0+e90leiS7zeXRZyeizpWjFNeam06zJYKJNx+qQlc1xN/fmLvD9/gz3V0KMt5bTVCJFeDEVa7kMi0aGUCJDq0wmLfFZJGblc9gKU7EXy2nS5SAUZ+MsUxDvNhPxmmgoU9GuyyUatPPjR/NsXhLorz1FgzoVc14y5qIUBBEslMowvCBF0lyWiatCiUevZNipx67Nw6XNItRSTkjQsOisYaFehzlfzuNHDzjYihBv0xBq1tBxNpdzBanoVUnUlqRhKZMjGXBV0iKatYrbUV1waOiqyCLSpGG90cx+n5MJUwnBlwr4+8/HHOzNEnecYaG7musdRgZtanSKZKpyk6nTisBor4VW0dKpycSmzsByWoHfkMWrvWYm6w1seOsYNRRyf76Xvx7ucCfiIO7VMeXVM95cTF9NDtbCdNwGJeeKZUimggKtYlR3qQjLl4onpDPSqGN/LcZG0I27SIbXkMO7cQ+frgT5Yr2fN0esxFo13Ar72ZgeYMhxnkCdGlPhCSQzg1baSuU4CpPwVBawfn2Et5dj3FmfYnt6DPupEzjEvnN9Z9keq2f3mp3VwVrme6r54dtv+O3Jvzx9+g9//PIT8St9SOL9VpxFUvotJby1eoONuRC34qPsLoRYu3GVLksFnXXlLAVqmPdXiimqmenQsxf2sPbOx9gjO7RP7fLldz/z4e2bYsOumkO75fEhbq9ME7/cyRvTo+wtRbgZDXJ9rI/epkrCTg0zXu1hv1dazvDe0hh3Dx7gmNjCEdvlq4eP2I4PIgn7qvCWn2RzcZLl2BCTw14WJobYWAgzG+pisNPGJZ/AReE0fr2CTn2WKJDI3mKM/+feZ1/z+t3PefL7r3QL5UiueSoYsFWw+doksQEXF31Wopd72FqMEu53ccF9nmG/DSFXjinjGFXyRIxpR1GrlLR52gkEBtje2eWTD97HpExCEnJpCYkWm4sR0cRCT5OR1fgVokMegu31BHw2OltMaOTJIjQFu0qKt1RBgUxKwrFEZDI5J7NVyI4fwWdUikCPkas9LcyOddFr19Ml2oYDrfgaDAS6HLzsb6TZrEYtTxBfLZW6PDlebT56hZTsZ46QkXCUjKTjaHNSaDeq+A+/5A2fpWFq9AAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/21de7aa5676ecf81e6a64ae81d26984d/507e6/2024-07-12-cover.webp 200w,
/static/21de7aa5676ecf81e6a64ae81d26984d/28a80/2024-07-12-cover.webp 400w,
/static/21de7aa5676ecf81e6a64ae81d26984d/8d2ea/2024-07-12-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/21de7aa5676ecf81e6a64ae81d26984d/36ca5/2024-07-12-cover.png 200w,
/static/21de7aa5676ecf81e6a64ae81d26984d/a3397/2024-07-12-cover.png 400w,
/static/21de7aa5676ecf81e6a64ae81d26984d/a331c/2024-07-12-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/21de7aa5676ecf81e6a64ae81d26984d/a331c/2024-07-12-cover.png&quot;
            alt=&quot;2024 07 12 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Last week, I announced &lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt; - Mongo, but it was on PostgreSQL. So, the Node.js library allows using PostgreSQL as a document database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Today, I have at least an equally big announcement: I released the PostgreSQL event store for &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html&quot;&gt;Emmett&lt;/a&gt;. Boom!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What’s Emmett? It’s an Event Sourcing library. I announced it some time ago and have worked on it continuously for the last few months. It already supports EventStoreDB, and now it has our favourite PostgreSQL storage!&lt;/p&gt;
&lt;p&gt;Read more:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/introducing_emmett/&quot;&gt;Announcing Emmett! Take your event-driven applications back to the future!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/introducing_emmett/&quot;&gt;Testing Event Sourcing, Emmett edition&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;How to use it? Pretty simple. Start with installing npm package:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; @event-driven-io/emmett-postgresql&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then setup event store using connection string to PostgreSQL:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getPostgreSQLEventStore &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett-postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;postgresql://dbuser:secretpassword@database.server.com:3211/mydb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; eventStore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPostgreSQLEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connectionString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Internally, it uses the &lt;a href=&quot;https://node-postgres.com/&quot;&gt;node-postgres&lt;/a&gt; package with connection pooling. So you don’t need to do much more. Well, maybe besides gracefully closing it on the application closure:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; eventStore&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cool, but what you can do with it? Check &lt;a href=&quot;https://event-driven-io.github.io/emmett/getting-started.html#event-store&quot;&gt;Emmett docs&lt;/a&gt;. The same you can do with EventStoreDB storage you can do with PostgreSQL!&lt;/p&gt;
&lt;p&gt;Or you can actually do more with PostgreSQL, as…&lt;/p&gt;
&lt;p&gt;PostgreSQL has inline projections support. What are inline projections? They’re functions updating your read models in the same transaction as appending events. So either all was stored or nothing. Of course, you need to be careful with them as they can slow your appends, but they’re really useful. Async projections will come in future releases.&lt;/p&gt;
&lt;p&gt;Ok, but how to use it? Let’s say that you’d like to build a read model with a summary of your shopping cart:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ShoppingCartShortInfo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Transformation function could look like that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; evolve &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  document&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartShortInfo &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; type&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; data&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; event &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ProductItemAdded &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; DiscountApplied&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ShoppingCartShortInfo &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  document &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
          document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalAmount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
          event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;price &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        productItemsCount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
          document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItemsCount &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;productItem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;quantity&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        totalAmount&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;totalAmount &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;percent&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It’ll be run for each event of type &lt;em&gt;ProductItemAdded&lt;/em&gt; and &lt;em&gt;DiscountApplied&lt;/em&gt; that’s appended to the event store.&lt;/p&gt;
&lt;p&gt;Let’s say that you’d like to use Pongo and store it as a document in PostgreSQL, then you can define projection as:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCartShortInfoCollectionName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shoppingCartShortInfo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCartShortInfoProjection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoSingleStreamProjection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  collectionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; shoppingCartShortInfoCollectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  canHandle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;and register it through event store options:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; projection &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; getPostgreSQLEventStore &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;@event-driven-io/emmett-postgresql&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;postgresql://dbuser:secretpassword@database.server.com:3211/mydb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; eventStore &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getPostgreSQLEventStore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;connectionString&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  projections&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; projections&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;inline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    shoppingCartShortInfoProjection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    customProjection&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We’re saying that we’d like to update the &lt;em&gt;shoppingCartShortInfo&lt;/em&gt; collection using the &lt;em&gt;evolve&lt;/em&gt; function for the following set of event types.&lt;/p&gt;
&lt;p&gt;Internally, it’ll use the Pongo new feature: a handler that loads the existing document and tries to insert, replace or delete it depending on the result obtained from the function.&lt;/p&gt;
&lt;p&gt;It look’s as follows:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; collection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;Document&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;collectionName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; event &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; events&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getDocumentId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;evolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;document&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you’re wondering what &lt;em&gt;getDocumentId&lt;/em&gt; is, then for &lt;em&gt;pongoSingleStreamProjection&lt;/em&gt;, it’ll automatically use the stream name as the document id.&lt;/p&gt;
&lt;p&gt;Suppose you’d like to customise it, e.g. to match events from different streams. In that case, you can use the &lt;em&gt;pongoMultiStreamProjection&lt;/em&gt; definition, which allows you to specify the document ID matcher for each event. For instance:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCartShortInfoCollectionName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;shoppingCartShortInfo&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; getDocumentId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;  ProductItemAdded &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; DiscountApplied&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;streamName&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; shoppingCartShortInfoProjection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoSingleStreamProjection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  collectionName&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; shoppingCartShortInfoCollectionName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  evolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  canHandle&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;ProductItemAdded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;DiscountApplied&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  getDocumentId
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can also do a free-hand projection using &lt;em&gt;pongoProjection&lt;/em&gt; that takes the following handler:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pongo&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; PongoClient&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; events&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; ReadEvent&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;EventType&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cool, isn’t it?&lt;/p&gt;
&lt;p&gt;**Read more details in the follow up article &lt;a href=&quot;/en/emmett_projections_testing&quot;&gt;Writing and testing event-driven projections with Emmett, Pongo and PostgreSQL&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Of course, those are still experimental features; they need to be optimised, tested extensively, etc. But they work, which makes me happy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As you see, I’m quite thrilled that I could deliver it, as this is a big milestone.&lt;/strong&gt; This will enable many people to finally do Event Sourcing in Node.js using PostgreSQL and have basic building blocks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;All of that wouldn’t be possible with my recent changes to Pongo.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;I managed to close the basic coverage of document manipulation methods.&lt;/strong&gt; I added initial versions of what was initially missing: replaceOne, drop, rename, countDocuments, count, estimatedDocumentCount, findOneAndDelete, findOneAndReplace, findOneAndUpdate, etc.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, a bigger portion that were made as preparations to Emmett PostgreSQL projections you just learned about:&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Added option to inject external connection pool and db client to Pongo collection as the first step for transaction handling.&lt;/strong&gt; Now, you can create transactions outside and inject pool clients. It’s not yet fully the same as Mongo API; it’ll be delivered in follow-up PR.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Strengthened the schema and updated the id from UUID to text.&lt;/strong&gt; Changed _id type to TEXT. In PostgreSQL, this should be almost equally the same indexable. Of course, it’ll take a bit more storage, but let’s live with that for now. Changing from uuid to text will allow more sophisticated key strategies. Most importantly, it’ll reuse the stream ID as a document ID for Emmett projections.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Also, thanks to the Franck Pachot contribution, we confirmed that Pongo is compatible not only with vanilla Postgres but also databases like Yugabyte!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It was just a week, but I’m extremely happy with how Pongo was taken by the community.&lt;/strong&gt; I got to &lt;a href=&quot;https://news.ycombinator.com/item?id=40897518&quot;&gt;HackerNews front page&lt;/a&gt; and got over 900 GitHub stars!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now it’s the time for Emmett!&lt;/strong&gt; Synergy with Pongo should help with that.&lt;/p&gt;
&lt;p&gt;What a week! It’s easy to forget that Pongo was released just 7 days ago!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Now I can go to vacations, next blog will be in August!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Pongo - Mongo but on Postgres and with strong consistency benefits]]></title><description><![CDATA[Flexibility or Consistency? Why not have both? Wouldn’t it be great to have MongoDB flexible schema and PostgreSQL consistency? MongoDB is a…]]></description><link>https://event-driven.io/en/introducting_pongo/</link><guid isPermaLink="false">https://event-driven.io/en/introducting_pongo/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a85203c2ed52d88de17d5d70d5209510/a331c/2024-07-07-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4eAAAuHgGqqWO7AAACEElEQVR42q1TzW4SURi9L1BSpa0FHOYfOlUp/7FWSNyQiaGABabODBSYQGktK9dNWl24qGvjC5iIT4DRtSvjC7gjjQ9RN8d7bztVq0266OLkm8z97jnn+7lEljVcJ0g0KkEUZUiSQqFe+aKqxqAo+r+EhpFCPJ6ArhvQtCWedFkygyRpiEREzM3dgiBIPPcvwsflIUpmH5a9j9UHFczPLyAUEqhrhSbrF8hUTpDJ5DAc7mBtrUBzI9RI/DehaZpwHBdP7Tb6/QEqlQparRYSiSQlD3MXfjK7GAjM4ujoNabTKSzLAiEEofCpAU7oeR5c18H6ehmbmxY6nQ5Goz00m02Uy2Xq4iF3xlrAIAgixuMxfp6c4PDFS+SzWbi2DcO4y0mJ49io1Z6g0WhwZxsbdZRKJies1WooFIrnhCwuL9/DwcEhjo9/4Mvnj3j7YYLJ1+8YDrYRpH0ljMimCqxU5nRrq83VFhdv83IZfDLlrI/B4AJePd/F/k4Pb95P8O7TN4ye7eEm/U8YQbvdomQu6vU6dWZxp57Xw2DQR6/X5SLVavV88ooaxx1NoN8qup0uH1AymUE0KoOkUlnk86t0cnkUi494TKdzyOXug52xuLKS5qX+uUqiHONuA4EbmJmZRZgOhp0Tlar5+yfLOt3HpbM9jPF4umc6V//fXrJ8Bl+MMBUf/q5dBCvtyk/vut/yL2ynos5QNT+bAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/a85203c2ed52d88de17d5d70d5209510/507e6/2024-07-07-cover.webp 200w,
/static/a85203c2ed52d88de17d5d70d5209510/28a80/2024-07-07-cover.webp 400w,
/static/a85203c2ed52d88de17d5d70d5209510/8d2ea/2024-07-07-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/a85203c2ed52d88de17d5d70d5209510/36ca5/2024-07-07-cover.png 200w,
/static/a85203c2ed52d88de17d5d70d5209510/a3397/2024-07-07-cover.png 400w,
/static/a85203c2ed52d88de17d5d70d5209510/a331c/2024-07-07-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/a85203c2ed52d88de17d5d70d5209510/a331c/2024-07-07-cover.png&quot;
            alt=&quot;2024 07 07 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flexibility or Consistency?&lt;/strong&gt; Why not have both? Wouldn’t it be great to have MongoDB flexible schema and PostgreSQL consistency?&lt;/p&gt;
&lt;p&gt;MongoDB is a decent database, but it gives headaches with its eventual consistency handling. I wrote about it a few times in past:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/&quot;&gt;Dealing with Eventual Consistency and Idempotency in MongoDB projections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/long_polling_and_eventual_consistency/&quot;&gt;Long-polling, how to make our async API synchronous&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/testing_event_driven_projections/&quot;&gt;How to test event-driven projections&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don’t get me wrong, eventual consistency is fine. We need to learn to live with that, still… Undeniably, having strong consistency guarantees, transactions, read your own writing is great.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On Friday, I decided to spend my working day on the small proof of concept that I called &lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What’s &lt;a href=&quot;https://github.com/event-driven-io/Pongo&quot;&gt;Pongo&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It’s a MongoDB-compliant wrapper on top of Postgres.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can setup it like that:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; pongoClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@event-driven-io/pongo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;postgresql://dbuser:secretpassword@database.server.com:5432/yourdb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongoClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;postgresConnectionString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongoDb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoDb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;users&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It will start internally with a PostgreSQL connection pool connected to your selected database.&lt;/p&gt;
&lt;p&gt;Having that, you can then perform operations like:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; anita &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Anita&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Inserting&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;roger&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cruella&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; insertedId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alice&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; anitaId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; insertedId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Finding by Id&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; anitaFromDb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; anitaId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Updating&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; anitaId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $set&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;31&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Deleting&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deleteOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; cruella&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_id &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Finding by Id&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; anitaFromDb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; anitaId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Finding more&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $lt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Internally, it’ll set up the collection as the PostgreSQL table with the key-value structure:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;IF&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;EXISTS&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;YourCollectionName&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    _id           &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;           &lt;span class=&quot;token keyword&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;KEY&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;          JSONB          &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    metadata      JSONB          &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _version      &lt;span class=&quot;token keyword&quot;&gt;BIGINT&lt;/span&gt;         &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _partition    &lt;span class=&quot;token keyword&quot;&gt;TEXT&lt;/span&gt;           &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;png_global&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _archived     &lt;span class=&quot;token keyword&quot;&gt;BOOLEAN&lt;/span&gt;        &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;FALSE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _created      TIMESTAMPTZ    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    _updated      TIMESTAMPTZ    &lt;span class=&quot;token operator&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;NULL&lt;/span&gt;     &lt;span class=&quot;token keyword&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Essentially, it treats PostgreSQL as a key/value database.&lt;/strong&gt; Sounds familiar? Yet, it’s a similar concept to &lt;a href=&quot;https://martendb.io/&quot;&gt;Marten&lt;/a&gt; or, more correctly, to AWS DocumentDB (see &lt;a href=&quot;https://www.enterprisedb.com/blog/documentdb-really-postgresql&quot;&gt;here&lt;/a&gt; or &lt;a href=&quot;https://news.ycombinator.com/item?id=18870397&quot;&gt;there&lt;/a&gt;, they seem to be using Mongo syntactic sugar on top of AuroraDB with Postgres).&lt;/p&gt;
&lt;p&gt;I explained in &lt;a href=&quot;/en/strategy_on_migrating_relational_data_to_document_based/&quot;&gt;general strategy for migrating relational data to document-based&lt;/a&gt; that contrary to common belief, document data is structured but less rigidly, as in the relational approach. JSON has structure, but it is not enforced for each document. We can easily extend the schema for our documents, even for specific ones, by adding new fields. We should also not fail if the field we expect to exist, but doesn’t.&lt;/p&gt;
&lt;p&gt;Handling semi-structured data in a relational database can be tricky, but PostgreSQL’s JSONB data type offers a practical solution. Unlike the plain text storage of the traditional JSON type, JSONB stores JSON data in a binary format. This simple change brings significant advantages in terms of performance and storage efficiency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The binary format of JSONB means that data is pre-parsed, allowing faster read and write operations than text-based JSON.&lt;/strong&gt; You don’t have to re-parse the data every time you query it, which saves processing time and improves overall performance. Additionally, JSONB supports advanced indexing options like &lt;a href=&quot;https://pganalyze.com/blog/gin-index#postgresql-jsonb-and-gin-indexes&quot;&gt;GIN and GiST indexes, making searches within JSONB documents much quicker and more efficient&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Moreover, JSONB retains the flexibility of storing semi-structured data while allowing you to use PostgreSQL’s robust querying capabilities. You can perform complex queries, joins, and transactions with JSONB data, just as you can with regular relational data.&lt;/p&gt;
&lt;p&gt;This flexibility, performance, and consistency combination makes PostgreSQL with JSONB a powerful tool. There are &lt;a href=&quot;https://info.enterprisedb.com/rs/069-ALB-339/images/PostgreSQL_MongoDB_Benchmark-WhitepaperFinal.pdf&quot;&gt;benchmarks showing that it can be even faster than MongoDB&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Still, the syntax is not the most pleasant (to say mildly). Just &lt;a href=&quot;https://www.postgresql.org/docs/current/functions-json.html&quot;&gt;check the docs&lt;/a&gt; or see what Pongo does behind the scenes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For instance, the MongoDB update syntax:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongoCollection &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoDb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;users&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; someId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $push&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; tags&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;character&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;will be translated to:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;users&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; jsonb_set&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;{tags}&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;COALESCE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;tags&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;[]&apos;&lt;/span&gt;::jsonb&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; to_jsonb&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;character&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; _id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;137ef052-e41c-428b-b606-1c8070a47eda&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Or for query:&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&quot;address.history&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $elemMatch&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; street&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Elm St&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;will result in:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sql&quot;&gt;&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;users&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;WHERE&lt;/span&gt; jsonb_path_exists&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&apos;$.address.history[*] ? (@.street == &quot;Elm St&quot;)&apos;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I thought that it’d be much easier if you could reuse your muscle memory from working with Mongo and use familiar syntax to access the data. I even prepared the compliant shim:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;typescript&quot;&gt;&lt;pre class=&quot;language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; MongoClient &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@event-driven-io/pongo&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; v4 &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; uuid &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;uuid&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; connectionString &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;postgresql://dbuser:secretpassword@database.server.com:3211/mydb&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongoClient &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;MongoClient&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;postgresConnectionString&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pongoDb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoClient&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pongoDb&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token generic-function&quot;&gt;&lt;span class=&quot;token function&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;token generic class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;User&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;users&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; anita &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Anita&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Inserting&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; insertedId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insertOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;alice&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; anitaId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; insertedId&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Updating&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;updateOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; anitaId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $set&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;31&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Deleting&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deleteOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; cruella&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;_id &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Finding by Id&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; anitaFromDb &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;findOne&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; _id&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; anitaId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Finding more&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; users &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; pongoCollection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; age&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; $lt&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;40&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toArray&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And guess why? You can already use it by installing the package:&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;shell&quot;&gt;&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @event-driven-io/pongo&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Why did I create Pongo? There are two reasons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First, I’ll need it for &lt;a href=&quot;https://event-driven-io.github.io/emmett/&quot;&gt;Emmett&lt;/a&gt; read models.&lt;/strong&gt; I was a bit silent about updates to Emmett as I’m working on adding subscriptions and streaming capabilities. The ongoing &lt;a href=&quot;https://github.com/event-driven-io/emmett/pull/76&quot;&gt;Pull Request&lt;/a&gt; went a bit out of hand, and I was a bit worn out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thus, the second reason. Sometimes, you just need to have fun.&lt;/strong&gt; We too often forget about it. That’s also why I came up with the name, a mixture of Mongo and Postgres and a reference to &lt;a href=&quot;https://disney.fandom.com/wiki/Pongo&quot;&gt;one of my favourite children’s movie characters&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is it production-ready?&lt;/strong&gt; You know the answer. What’s there works fine, but it’s far from having a fully compliant MongoDB API. And it might not have it fully, but &lt;a href=&quot;https://en.wikipedia.org/wiki/Pareto_principle&quot;&gt;Pareto principle&lt;/a&gt; works here. I also hope that I’ll get from you or others who decide to use its contribution or sponsoring to bring it to the expected level.&lt;/p&gt;
&lt;p&gt;Still, it can already do the most needed operations, so you should be fine with trying it!&lt;/p&gt;
&lt;p&gt;I’m planning to do a series of articles on the internals of building such a tool!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I’m curious about your thoughts on it. Yay or nay?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;
&lt;p&gt;Oskar&lt;/p&gt;
&lt;p&gt;p.s. &lt;strong&gt;Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.&lt;/strong&gt; You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to &lt;a href=&quot;https://www.icrc.org/pl/donate/ukraine&quot;&gt;Red Cross&lt;/a&gt;, &lt;a href=&quot;https://savelife.in.ua/pl/donate/&quot;&gt;Ukraine humanitarian organisation&lt;/a&gt; or &lt;a href=&quot;https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone&quot;&gt;donate Ambulances for Ukraine&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Filtering EventStoreDB subscriptions by event types]]></title><description><![CDATA[Regular expressions are one of the classic examples of hate and hate relationships. Yes, it’s not a typo; hate and hate. Do you know anyone…]]></description><link>https://event-driven.io/en/filtering_eventstoredb_subscriptions_by_event_types/</link><guid isPermaLink="false">https://event-driven.io/en/filtering_eventstoredb_subscriptions_by_event_types/</guid><content:encoded>&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto&quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ac7ebf197e1a9ec135ecce515f6c4edb/a331c/2024-06-30-cover.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4eAAAuHgGqqWO7AAACTElEQVR42mNgIAUwMjIwwxA64OTkkJKS4uPjk5CQYGNjExIS4uDg4ObmFhYWBgoyMjJiNw8iwc/Pb29nHxISYmtrKygoaG9vLy8vLy4ubmxsbGVpycDEqi/MkKLFEK/BEK3GwM/GwMDKysjBDtTJqOPCYBHEZqBvbGdnZ2VlZWFhAWRYW1vb2Ng4ODjYWlsysPDUmTH8L2f8X8DwJZtBQ5CBwcmdoXUlS+l6nsxFDBnTeSNjQny8fZOSkmJjY7Ozs1NSUuLj4zMyMvLz8tS1dRU5GcLVmAKVGHwVGHhYGRi6Dwksum01dUtM5U4mx1CpsKDY6JhooJ7U1NTMzMzExEQgOyYmJjkhnltEuliX4UMq46M4hquRDPK8DAw9x5WbZoVVL9Wu3ybpHCFb2ZaQXOCVEJuSnZ2Vk5MDMSU6OjozPc3WxTNckWGbD+Nqd4aFzgziXAwMmQsZmnfJVm3n6j0jUb6RK2e6dExiSEx0XGZmRl5eXlpaWlxcHDAIE+LjZBSUDYQY8vUZM3QYkrUYBNgZGHiEGOInME+/rDrtnF7TfsGYfJustMK4+NjIyEigzUCdQJeDNMdGcwhJVRsz/C9j/J/P8CULHGBAIKvL0HJAJG0Kv7E3e0Ssf052XkxsDDCcgNYmgkF5eXlQYKC4pLQ4G4OVBKOZGIOJKAMnMziSgUDZlElEiUFHwyQxMRkYzmFhYQkJCTFgEBwcHBUVlZycDEw54ETBiD31AVOYpqamFhhoa2sD2UASwgWy2dmBvsSWPBmZGHCZiAcAAMqmmFZtaGyPAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;picture&gt;
          &lt;source
              srcset=&quot;/static/ac7ebf197e1a9ec135ecce515f6c4edb/507e6/2024-06-30-cover.webp 200w,
/static/ac7ebf197e1a9ec135ecce515f6c4edb/28a80/2024-06-30-cover.webp 400w,
/static/ac7ebf197e1a9ec135ecce515f6c4edb/8d2ea/2024-06-30-cover.webp 800w&quot;
              sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
              type=&quot;image/webp&quot;
            /&gt;
          &lt;source
            srcset=&quot;/static/ac7ebf197e1a9ec135ecce515f6c4edb/36ca5/2024-06-30-cover.png 200w,
/static/ac7ebf197e1a9ec135ecce515f6c4edb/a3397/2024-06-30-cover.png 400w,
/static/ac7ebf197e1a9ec135ecce515f6c4edb/a331c/2024-06-30-cover.png 800w&quot;
            sizes=&quot;(max-width: 800px) 100vw, 800px&quot;
            type=&quot;image/png&quot;
          /&gt;
          &lt;img
            class=&quot;gatsby-resp-image-image&quot;
            src=&quot;/static/ac7ebf197e1a9ec135ecce515f6c4edb/a331c/2024-06-30-cover.png&quot;
            alt=&quot;2024 06 30 cover&quot;
            title=&quot;&quot;
            loading=&quot;lazy&quot;
            decoding=&quot;async&quot;
            style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
          /&gt;
        &lt;/picture&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Regular expressions are one of the classic examples of hate and hate relationships.&lt;/strong&gt; Yes, it’s not a typo; hate and hate. Do you know anyone who loves or knows how to write moderately complex regex? And can they keep their skill for longer than two weeks without forgetting how to do it?&lt;/p&gt;
&lt;p&gt;Maybe this whole wave of Large Language Models is about having someone who will show us how to write regexes. Maybe we hate regular expressions so much that we don’t care about hallucinations.&lt;/p&gt;
&lt;p&gt;Still, undeniably, regular expressions are useful and powerful. Let me show you an example, but be careful; I warned you already!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We’ll use in this article knowledge from my two other articles:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/&quot;&gt;Persistent vs catch-up, EventStoreDB subscriptions in action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/event_stores_are_key_value_stores/&quot;&gt;Event stores are key-value databases, and why that matters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/en/lets_talk_about_positions_in_event_stores/&quot;&gt;Let’s talk about positions in event stores&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;how_to_map_event_type_by_convention/&quot;&gt;Mapping event type by convention&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Don’t want to dive deep into them? Fine, here’s the TLDR:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EventStoreDB is a database that has the durable append-only log as the physical structure where events are stored,&lt;/li&gt;
&lt;li&gt;events are grouped into streams. Stream is a key/value pair where the key is the record id (e.g. order id), and the value is a sequence of events recorded for this record (e.g. &lt;em&gt;OrderConfirmed&lt;/em&gt;, &lt;em&gt;OrderPaymentRecorded&lt;/em&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Events have specific data and metadata, e.g. event type name; we can use it to deserialise events to a specific type. We can do manual type resolution, or automatic, based on conventions.&lt;/li&gt;
&lt;li&gt;EventStoreDB has a functionality called &lt;a href=&quot;https://developers.eventstore.com/clients/grpc/subscriptions.html#subscribing-from-the-start&quot;&gt;Catch-up subscriptions&lt;/a&gt;, which allows you to subscribe to push notifications about new events. You can subscribe to all events or those coming from specific streams.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Today, we’ll focus on subscribing to all events and how to filter them by event type.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can think about EventStoreDB subscription as some moving cursor. They start by pointing to the selected position (e.g., the start of the log) and then move sequentially event by event. The global log (called in EventStoreDB &lt;em&gt;$all&lt;/em&gt; stream) represents the physical file storage. Thus, it’s fast as it doesn’t go to indexes. The safe default recommendation is to start from the global log if you want to get events from multiple streams. Typically, you do, as you may want to trigger some flows, build read models, or forward events to the messaging system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;By default, you’ll get all events, whether you need them or not, including internal events used for internal store processing.&lt;/strong&gt; That’s a waste of network and resources. Not surprisingly, the mature database should provide a solution for that, and that’s what EventStoreDB do with